Vote for the Lecture 10 topic here by Friday 11:59PM ET
Normally, to run both the backend and frontend, we have to do something like this:
We can make managing the frontend and backend easier by creating a package.json in our root directory and declaring a workspaces!
One advantage of doing this is that dependencies are installed at the root, so if both your frontend and backend use the same version of TypeScript, it is only installed once at the workspace root.
Another thing you may notice is that only one lockfile is generated and it is placed at the workspace root with
Having your package manager at your root also makes running scripts from both workspaces much simpler. To run the respective
start script for the frontend or backend, you can
yarn workspace frontend start or
yarn workspace backend start
If there is code that you wish to be shared between both your frontend and backend, for example, you can also declare that as another workspace in your
and include it in the respective frontend and backend
package.json like so:
- Yarn workspaces allows your dependencies to be linked together, making it easier to manage a monorepo
- Having one lockfile creates fewer conflicts and easier code reviews
- Reduces redundancy in packages
- Including workspaces as dependencies makes it easier to test your utility functions locally before you publish them online
We will be showing Yarn workspaces setup on our example Songs App we've been working on throughout the semester. After lecture 8, we bridged the frontend and the backend together to fetch data from our API endpoints defined in
backend/index.ts to show in the frontend UI.
We kept these two folders separate and we showed the old way of
cd into each folder and running Yarn install. Now with workspaces we can manage the package from the root and directly run
yarn install once to install all frontend and backend dependencies!
To do this, our root
package.json will define the workspaces. (Note: root means outside of both the frontend and backend folders, on that same level of the directory hierarchy.)
Most important lines here are 3-7 which define the workspaces and set
private: true since workspaces can't be published.
Now our backend workspace will look like the following. I've omitted some of the dependencies part for brevity you can find the full file below
Our frontend workspace will also look like the following. The full file can also be found is very similar to the default
package.json from create react app we have been using all along.
Now we can profit from all our hardwork setting up by managing everything from the project root. In the root we can run:
yarn workspace frontend build to build our frontend application. This is equivalent to running
react-scripts start based on
We can also run
yarn workspace backend build to build our backend application. This is equivalent to running
tsc -p tsconfig.json based on
In general the syntax for running a script for a workspace is
yarn workspace <workspace_name> <script_name>. Where
<workspace_name>/package.json has a value for
<script_name> in the
We notice we have two build scripts in backend and frontend.
yarn workspace run build will tell yarn to run build script in all workspaces that have them.
Of course, we can also run
yarn install in the project root to install all the dependencies required in all of our workspaces. If there are duplicates, yarn will only install the dependency once.
Yarn workspaces is super cool and we just covered a subset of functionality. If you want to learn more check out these links:
One of the best parts about Firebase is you can use Sign in with Google/Facebook/GitHub/etc! This way you don't have to deal with usernames and passwords yourself!
Before writing any code, you need to setup Firebase Authentication first.
In your project's firebase console, click on the authentication icon below the settings icon:
Then click on the sign-in method tab and enable Google login by following the setup instructions:
We did a Live Coding Demo here based on the Songs example from last week. I will include the files changed here.
To handle authentication we made a wrapper component
Authenticated to handle all Authentication:
We then wrap our whole
SongList app in
If the user is logged in,
SongList will show. Otherwise they will be asked to log in.
We then deployed this app on Firebase for the frontend and Heroku for the backend. Refer to the commands above.
We recommend using Firebase's built-in support for Google Authentication since storing passwords securely is very hard (more details in the security section). Firebase also has support for many other accounts such as Facebook, GitHub, Microsoft, Apple, etc. Read more about Firebase Authentication here. If for whatever reason you really want to implement your own user accounts, Firebase can still help you store passwords securely. Read more here.
Web app security is integral in a world where people have malicious intentions. As such, we advocate for at least these basic security measures.
Do NOT store passwords in plain text in your database!!
If somehow there were a vulnerability in your database, the passwords of all of your users would be directly exposed to hackers. Moreover, many people reuse their passwords for some if not all other, so you could also compromise something like someone's banking credentials or emails.
While it is safer to store hashed passwords, it is very easy to do it incorrectly, which is why we teach firebase authentication!
Input sanitization 'cleanses' inputs so that they can not be used in unintended ways.
For example, it may seem reasonable to add every comment on a blog into a database entry like this:
“INSERT INTO 'comments' (comment) VALUES '” + user_input + “';”, but it is actually incredibly unsafe
user_input were something like
'; DROP TABLE comments;.
In general you cannot assume your endpoint will only be used by your frontend.
Recall that we used Postman at the beginning of the semester to test Express endpoints. As such, it is always important to verify your inputs and authenticate your requests!
Going hand-in-hand with the last point, when a request is made to your backend, make sure that the source of your request has the sufficient permissions to perform whatever action is being asked!
For example, if the OfficeHours app recieved a request to edit the times to someone's office hours, the backend would check that this person is indeed the TA who owns that OH and not a student.
In lecture, we showed how to hack our very simple application by sending in bad unauthenticated input data. This caused our frontend to break a little bit. Check the lecture video for details!
We fixed the backend by using Firebase Admin's verifyIdToken function to check whether the user had logged in or not.
On the backend, we had the
post endpoints check the
idtoken in the request headers.
admin.auth().verifyIdToken(req.headers.idtoken as string) and only then do we allow the input to be saved. Otherwise, we log the error.
Now when we send the request back, we need to send the idtoken with the request header. So in the
updateRating methods that called the POST endpoints above, we send call
firebase.auth().currentUser?.getIdToken(true) to get the user's idtoken. Then we pass it in as a field to
headers in the fetch. However, if the currentUser is undefined for some reason (what the
? catches), then we console that the user isn't authenticated. Everything else should be familiar from last lecture.
There is a lot more we could've done in terms of security here.
- We should send back error messages to the user about why requests are failing instead of just doing
console.log()since the user won't see that. This would be bad user experience since they don't have feedback about why things aren't working.
- We should also do input validation on the backend to ensure we are getting our expected inputs. What if we aren't passing an object that looks like:
To deploy your web application means to put it on a Web server so others can access it via the internet. We will deploy both our frontend and backend on Heroku. There are a variety of services you can use for deployment including Firebase, Amazon AWS, Microsoft Azure, etc, but we decided to show you Heroku deployment since it is easier and requires less setup.
We will be taking our songs app we have been building throughout the course and deploying it to a remote server. To deploy your own app (for final project for example), you can follow the same steps usually.
To deploy to Heroku we need some additional setup. We will be serving our frontend via our backend so express should grab files from the
frontend/build folder. In the frontend folder, when you run
yarn build, you will get a
build directory containing all the optimized, bundled frontend React code ready to be pushed to a remote server. We will include the line
app.use(express.static(path.join(__dirname, '../frontend/build'))); to do this. (Note: this requires us to
import path from 'path';)
We will also be calling upon our express app to use CORS to allow cross origin requests. If your backend requests are being blocked be of some
CORS cross origin policy issue you probably forgot to include the
app.use(cors()); line. (Note: this requires us to
import cors from 'cors';)
The beginning of your
backend/index.ts should look like the below. Note the CORS line (line 12) and express static serving line (line 13) we described above and the relevant import statements.
Notice we are having
app.listen listen for either port 8080 or
process.env.PORT. Previously, we hardcoded
8080 for this parameter.
process.env.PORT will be defined by Heroku and we want the app to listen for requests on that port in the deployed site.
tsc the TypeScript compiler to compile the
index.js. You should see a new
index.js file after you run
We are using the same
tsconfig.json as found in lecture2.
No further setup required in our frontend folder.
package.json will look like the following. We are using yarn workspaces as described above and the
heroku-postbuild script will build both workspaces to prep them for push to remote server. (We discussed
yarn workspaces run build above)
heroku-postbuild is a special command recognized by Heroku to allow you to customize the build process.
package.json can have dependencies and yarn will install them with
yarn install just like everything else. You will just need to run
yarn add -W <pkg_name> since just using
yarn add will show a warning message asking if you are sure about adding a dependency to the root. We add Heroku in this case to help with deployment!
Read more about
We will also have Procfile that tells Heroku what script to run to start the application. In this case we will be using the backend
node index.js to start the backend listening for requests and serving the frontend assets from the
Read more about the Heroku Procfile here.
We will now show the series of commands to deploy to Heroku. We will be using Heroku Git to deploy.
Visiting the URL should take you to the same application you had locally. Now you can share that link with your friends so they can visit your website too! Take a look at our deployed site here: https://webdev-demo-fa20.herokuapp.com/ (If you have issues deploying feel free to submit this link for the attendance question, but please do come to office hours first!)
If you run into issues that the app isn't authorized to use authentication, go to your Firebase project on firebase.google.com > Go to Console > [your project] > Authentication [on sidebar] > Sign-in Method > Authorized domains > Add Domain and enter the URL of your deployed site.
In your final submission of your final project, we want you to follow these steps to deploy your app to Heroku and place the link in the
README.md. If you prefer to deploy on another platform feel free but we do not guarantee we know how to debug any issues that come up.