I recently went through the process of deploying my final project for Flatiron, as well as helping a classmate of mine deploy her project as well. Mine is called Nature Watch and it allows users to add Sightings of flora and fauna they see in nature and see what others have recorded and leave comments on public sightings.
The app worked great locally and deploying the frontend and backend separately wasn't too bad, but getting the two to work together was a bit of a challenge and I struggled in particular getting everything set up so that I could implement the sessions and cookies authentication system I used that comes with Rails. My classmate also had some similar but different issues that we had to troubleshoot. There are many blogs out there that share how to deploy a Rails API with a React frontend, but very few discussed steps needed to include sessions and cookies, so I wanted to share the steps I took, the troubleshooting we had to do, and what ultimately worked for me.
First you need to make sure you have a Heroku account and follow the steps to add the Heroku CLI.
I had made my frontend and backend on two different repositories on Github since I knew I'd eventually be deploying them using two different apps on Heroku.
Deploy Backend
(Note: if you did not build your project using postgreSQL, you will need to reconfigure it to this so it can be hosted on heroku. You can check out my previous post for instructions on how to do that.)
- cd into your api directory
- In your console run 'heroku login'
- Once logged in run 'heroku create name-of-your-api' (mine was: 'heroku create nature-watch-api')
- At this point, I was notified that I had to add a credit card because the max number of apps allowed on Heroku without a card is 5. So I opted to delete a few practice apps that I had built.
- After the app was created the console returned to me my url: https://nature-watch-api.herokuapp.com/
- Once created you can build your app by running in console 'git push heroku main' --main was the name of the branch I wanted to push, in most cases it will be the master branch.
- After a successful build, you can create the Heroku database by running 'heroku rake db:schema:load'
- run 'heroku rake db:migrate'
- If you have seed data you can run, 'heroku rake db:seed'
- You can then check your endpoints and any json data that is returned: https://nature-watch-api.herokuapp.com/api/v1/sightings/1
- Note: this was a spot that we had to do a lot of trouble shooting for my friend's project.
The issue was that for her project, every time we navigated to an endpoint that we knew existed, a 500 error was returned with very little information about what was actually wrong. We ran 'heroku logs --tail' in console and the logs indicated that there was a no method error for nilNil class of goals but it was not specific for us to know where this was occurring. We eventually found this Stack Overflow that pointed us to a debugging tool. In the api config/environments/production.rb there exists the following line:
config.consider_all_requests_local = false
This ensures that full error reports are disabled which makes sense for a production app, but we actually wanted to see the errors so we then changed this to true.
config.consider_all_requests_local = true
- git add .
- git commit -m "Add local debug"
- git push heroku master
Once it was pushed to Heroku and we navigated to an endpoint we could see the normal rails errors that we were used to seeing, and it pointed to the exact issue. In the GoalsController in the action
def index
goals = current_user.goals
render json: goals, status: 200
end
it told us that there was no method goals for nilNil class. That was when we had our aha! moment and realised, of course there would be no data to show, because there is no user logged in and therefor no current_user. Since in her app there were no public routes not associated with a user, this was the case for all her endpoints. Once we figured this out, we changed her production.rb config.consider_all_requests_local back to false and continued moving on. It was a really great learning point of how having the right debugging tools is so important!
- After the API was deployed I changed the fetches in my frontend to reference the heroku URL. This was fairly quick to do since I had made a const for my base URL and that was the only change I had to make:
// const BASE_URL = 'http://localhost:3000/api/v1/'
const BASE_URL = 'https://nature-watch-api.herokuapp.com/api/v1'
const SIGHTING_URL = `${BASE_URL}/sightings`
const COMMENT_URL = `${BASE_URL}/comments`
- I then tested if my local frontend could successfully reference my heroku api by running 'yarn start' in console. I could successfully login and navigate to the different pages using the buttons, but noticed I had a third-party-issue that was raised and it said, “Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute”. I opted to continue with the deploying process and come back to this issue.
Deploy Frontend
- cd into your frontend directory. If you are using react-router for your frontend routes, you'll need to add where it needs to go upon initial page load:
- touch 'static.json' in the root directory and add the following:
{ "root": "build/", "routes": { "/**": "index.html" } }
- Once this is set up you can in console run 'heroku create nature-watch' and you should be returned your new url https://nature-watch.herokuapp.com/
- After you commit your changes you can run 'git push heroku main' (or master)
- Do a happy dance and take in a few basking breaths! You've done a lot of work and should have a deployed frontend and a deployed backend!
- At this point when I navigated to my frontend url and tried to login, I got an error saying the request was denied due to a cors issue. I then remembered that because I was using sessions and cookies I had to add the specific sites I wanted my API to accept requests from instead of using the * to indicate all sites. So I went ahead into config/initializers/cors.rb and added in the new frontend Heroku url to the allowed origins:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000', 'http://localhost:3001', 'http://localhost:3002', 'https://nature-watch.herokuapp.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
Once this was pushed to Heroku much of my site was working. I could login, signup, edit and delete both sightings and comments, and logout. However I kept getting an internal server error every time I tried to make a new sighting or comment and if I refreshed the page I was logged out. Neither of these were problems when running everything locally. Every time I made one of these requests the issue “Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute” that I mentioned above would appear.
After mush googling I knew that I somehow needed to add
same_site: :none, secure: true
somewhere in my code, but I could not figure out where. I then came across the blog "When Chrome Blocks Your Cookies" by Caleb on Medium. This was an incredibly helpful article that explained that this was a relatively new issue in which Chrome would reject cookies with SameSite set to none and not marked as secure. Turns out there is a gem that can handle this for us: rails_same_site_cookie !
Fixing SameSite Cookie Issue
- In the gemfile for your API add: gem ‘rails_same_site_cookie’, ‘~> 0.1.8’
- cd into your backend directory and run 'bundle install'
- git add .
- git commit -m “Add same_site_cookie gem.”
- git push
- git push heroku main
At this point when I navigated to my site, I found that I could now perform full CRUD on all of my models as expected and that refreshing the page would not log my user out, which meant that the cookie data was successfully being sent to my backend and grabbing the current_user from the server!
To recap, if you have sessions/cookies for your authentication, you will need to add your frontend url to your cors as an accepted origin and to allow cookie data to be sent from the frontend you will need to add the rails_same_site_cookie gem to your gemfile. If you are using react-router you will need a static.json file in your root directory indicating where it should go upon initial page load. A good troubleshooting tool lives inside the rails production file where you can set all requests to be treated as local to be true.
Hope this is helpful and can save some of you troubleshooting and researching time!
And a big shout out to my friend and classmate Jordan for working through all this with me! You can follow her on Medium.
Happy coding!
Top comments (3)
Hey Meks! I just graduated from Flatiron School as well and I followed your blog post to deploy my final project. I'm getting a 500 error when try to create new company or contribution and looks like the current_user id is nil. Could you share with me how you resolved Jordan's project issue with the backend getting nil class error, please?
Thank you!
So helpful and masterfully written friend :)
Awww, thanks, friend! That means a lot!