-
Using APIs from Enigma (a Government records resource), Google Maps, and Wikipedia, and deploying the App to Heroku
- Rails will handle all API calls to Google, Enigma, and Wikipedia. But React will call the Rails API for its data needs.
- this project on github
This App - What is It?
Endangered - An Interactive Map of Endangered Species in the U.S.
I pull a list of endangered and threatened species in the United States from [Enigma] which is not only a list of all endangered and threatened species, but also the name of every wildlife sanctuary and the state in which it’s located.
Next, I use Google’s [Maps API] to do a few things:
- Add latitude and longitude coordinates to the Enigma records
- Generate customized maps that display markers marking the locations of the protected area
- Markers, when clicked, will display all of the species threatened and/or endangered at that location
Then I use [Wikipedia’s API] to pull an image and a description of the species to display.
Using both Enigma and Google Maps requires signing up for an account and using an assigned key. It’s all free, but it adds friction to the process. I created a .env file and stored the keys in there. If you do the same, be sure to add .env to your .gitignore file so it won’t end up publically available on github.
Before you jump in make sure you have the latest Ruby, Rails, and Bundler installed. Bundler just updated to Bundler 2. Make sure you are current with a quick:
$ gem update --system
$ gem install bundler
All Set? Okay. Here’s what we’re going to do…
I. Setup - New Rails Project
II. Setup - New React Project
III. Rails Back-End
A. Models
B. Controllers
C. Using Faraday for API Calls
D. Manipulating Returned Data
E. Routes
IV. React Front-End
A. Add Dependencies: Semantic, Axios, google-maps-api
B. Containers and Components
C. Adding Redux - Application State
D. React Router
V. Deploying to Heroku
I. Setup - New Rails Project
Rails 5 lets us set up a new Rails project for use just as an api. We are going to tweak things a little bit more by using postgresql as our database as well as making some room for use of rspec instead of the Rails default TestUnit.
So, in our terminal we type:
rails new my-project --api --database=postgresql -T --no-rdoc --no-ri
Now switch into the project directory with cd my-project
. Get postgresql up and running on your computer and make sure the Rails server has no issues.
We can open it up on port 3001 with:
rails s -p 3001
If you get the NoDatabaseError
it just means rails hasn’t yet built the database. Try running rails db:create
and then try running the server again.
II. Setup - New React Project With create-react-app
Now we are going to make a new React project inside the Rails project!!!
We will call our React app ‘client’ and it will exist as a subdirectory of the Rails project. So, make sure you are in the parent directory of the Rails project when you proceed.
It’s pretty easy to use create-react-app
. A global install of create-react-app is now not the way to go. You can find a more detailed how-to here, but if you haven’t previously installed create-react-app globally then the simple instructions are to run the following in your terminal:
npx create-react-app client
cd client
yarn start
Now if you open https://localhost:3000 you will see the React logo and you’ll be looking at a working React app!
Also, right now both servers are running–Rails on port 3001 and React on port 3000
Let React Talk to Rails
We have to set up the React project to reach out to the rails backend when it makes api calls.
Open up the package.json
file located in the /client
folder and add:
"proxy": "http://localhost:3001",
*If you want to see what your package.json
folder will look like with this and all other dependencies, see this
One Command to Start Them All - Procfile & Heroku
Now the React app will use port 3001 for all api calls, but getting everything up and running is going to be a pain if we have to start everything seperately. We can take care of this! Here is how you fire everything up using one command:
Procfile
First, touch Procfile.dev
in the root folder of the Rails project. Then, inside of it add this:
web: PORT=3000 yarn --cwd client start
api: PORT=3001 bundle exec rails s
Heroku
We are going to use the heroku
CLI to manage the procfile. If you use homebrew
then that’s easiest. brew install heroku
and you are set.
Once you have the heroku
CLI installed, test it out by running:
heroku local -f Procfile.dev
That gets it up and running with one command, but that command is a drag to type everytime. Instead, touch ./lib/tasks/start.rake
and open it up. Inside you will need to put the following:
namespace :start do
task :development do
exec 'heroku local -f Procfile.dev'
end
end
desc 'Start development server'
task :start => 'start:development'
Now, when you are in the root directory of the Rails project you can simply run rake start
and your app will be up and running.
You can take it from here and build your own app! However, I’m going to walk through what I did when I used the APIs from Google Maps, Wikipedia, and Enigma along with how I folded in Redux, semantic-ui-react
, google-maps-react
and so much more…
III. Rails Back-End
A. Models
The relational data structure is pretty straight forward:
Species
has ahas_many
relationship withLocation
, and vice versaSpeciesLocation
has abelongs_to
relationship with aSpecies
and aLocation
- Each
Location
has abelongs_to
relationship with aState
- Each
State
has ahas_many
relationship withLocation
State
also has ahas_many
relationship withSpeciesLocation
and ahas_many
relationship withSpecies
So, I will have models for:
Species
Location
SpeciesLocation
State
I can generate these using scaffold in Rails.
rails g scaffold Species name:string location:string status:string desc:string imgsrc:string
rails g scaffold Location (name etc...)
rails g scaffold State (name etc...).
SpeciesLocation is a special case since it’s just for using with our has_many :through
relationship. We can strip it down even further and use:
rails g model SpeciesLocation species:references location:references
B. Controllers
Add some these files to app/controllers/concerns
- enigma.rb
and wikipedia.rb
.
- Controllers
- Locations Controller
- Species Controller
- States Controller
- Helpers/Concerns
- Class Methods
C. Faraday
Faraday makes GET and POST calls to an api a lot easier to handle than without. But first add this to the Gemfile:
gem 'faraday'
gem 'faraday_middleware'
D. Manipulating Returned Data
After using Faraday to get the data from Enigma/Google Maps/Wikipedia, I manipulate the data before sending it back to the Front-End. This includes:
- parsing names of places/species
- sorting data by value
- adding 2-letter state abbreviations
- Parsing names in particular ways for use with Google Maps
E. Routes
Every time I use axios
in React to make an api call I specify the url. This url matches a declared route in my Rails app which then sends it to the appropriate controller and function within that controller. Here’s my config/routes.rb
file:
IV. React Front-End
React is a JavaScript front-end library that lets you easily make single-page applications with an eye towards modularity and reuse of code.
A. Add Dependencies: Semantic UI, Axios, google-maps-api, logger, thunk, redux-devtools-extension
I’m adding react
and it’s many many dependencies using yarn
.
Redux pairs with react
to manage state in a centralized location. I’m adding redux
using yarn
as well. I’m also adding react-redux
which let’s the two play nicely together.
I’m assuming you have some familiarity with the concepts and have a basic understanding of react
and redux
. If not, check out the getting started guides for each.
All of these can be added from the root of the Rails project using yarn --cwd client add name
. Beyond adding react
and redux
I’m adding:
axios
for api callssemantic-ui-react
andsemantic-ui-css
for looksgoogle-maps-react
for help dealing with the Google Maps APIlogger
for a verbose console, letting me know what’s being called and servedthunk
to let us use middlewareredux-devtools-extension
to let us use redux devtoolsreact-router-dom
for handling url routes and keeping everything on a single page
After all of that my package.json
file looks like this:
Containers and Components
In my project I will want a folder structure that helps me keep track of all the kinds of components I’ll be dealing with. I’m using a definition of “if it has a state, it’s a container”. So, I made these folders to keep track of containers vs components, the store (a redux-related folder), css, and resources (.jpegs and the like):
src/components/
src/containers/
src/css/
src/store/
src/resources/
Also, I will use a few Google fonts so add a link to that library. Open client/public/index.html
and add <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
between the <head></head>
tags.
React Components
When a user first hits the homepage, I want to query the Enigma database and build it. So, I want the user to have something to look at but also keep the user from using anything that’s not ready yet.
So, I use componentDidMount
to dispatch a call to the store to get all the data from Enigma, parse it, create records in the database, and serve up some of the data back to the app, which it will use to set the application state.
C. Adding Redux - Application State
It is entirely possible for me to wire this app up without Redux, but there is a not negligible reason for using it and it’s good to get the practice. Yee.
[Redux] lets multiple components access the same state. It simplifies things tremendously if you have a large application with many, many components that need to display and/or modify state. With Redux, state isn’t kept within the component so it’s not actually modified by the component (i.e. we rarely if ever call setState
within a component).
But if we don’t call setState
, how do we set the state?!?!
The Store
Redux maintains a “Store” which contains the application-level state. We dispatch
actions
which calls a reducer
function which returns a fresh copy of the state with any changes the action caused. If we want to display the state within our component, we subscribe to the store’s state. This keeps our component current, as any update to that state will update the component.
Sound a little complicated? It is at first. But once it clicks it’s pretty clear. Here’s how it’s done:
- State -
mapStateToProps
: defining aconst
calledmapStateToProps
then usingconnect
to connect it to the store lets us pick and choose whichever pieces of the state we need and have them available as namedprops
. - Actions -
mapDispatchToProps
: defining aconst
calledmapDisptatchToProps
then usingconnect
to connect it to the store lets us pick and choose the actions we need to call within that component. The actions are available asprops
, just as if they’d been passed down from a parent component.
D. React Router
By using react-router
and react-router-dom
, we can easily define multiple url routes that render different components rather than render different pages. We drop a <Routes />
component within the <App />
component, so that navigation to the url will instead render the components defined in my client/src/routes.js
file Here is my routes file:
V. Deploying to Heroku - uh, not yet
I will come back and update this with more info once i get the bugs worked out - it seems my multiple API calls and API keys are more than i bargained for…
- add package.json to root with
yarn init
- add
Procfile
to root
Add to package.json:
{
"name": "endangered",
"license": "MIT",
"engines": {
"node": "13.6.0",
"yarn": "1.21.1"
},
"scripts": {
"build": "yarn --cwd client install && yarn --cwd client build",
"deploy": "cp -a client/build/. public/",
"heroku-postbuild": "yarn build && yarn deploy"
}
}
Add to Procfile
:
web: bundle exec rails s
release: bin/rake db:migrate
Resources Referenced
- React: https://reactjs.org [React]: https://reactjs.org
- Redux: https://redux.js.org [Redux]: https://redux.js.org
- Enigma: https://public.enigma.com [Enigma]: https://public.enigma.com
- Google Maps API: https://developers.google.com/maps/documentation/javascript/tutorial [Maps API]: https://developers.google.com/maps/documentation/javascript/tutorial
- Wikipedia API: https://www.mediawiki.org/wiki/API:Main_page [Wikipedia’s API]: https://www.mediawiki.org/wiki/API:Main_page
- Bundler 2: https://bundler.io/blog/2019/01/03/announcing-bundler-2.html [Bundler 2]: https://bundler.io/blog/2019/01/03/announcing-bundler-2.html
- Postgresql: https://www.postgresql.org/download/ [postgresql]: https://www.postgresql.org/download/
- google-maps-react: https://github.com/fullstackreact/google-maps-react
Resources Used
- A great post by Bruno Broem which is an update on another great post.
- This one from Charlie Gleason; it’s not only helpful, it’s entertaining.
- And this from the fullstackreact folks. It’s from 2016 but just about still works as described.