Building a Crypto Weed App: Part 3 - Going Live
How to deploy a Rails app to Heroku with Postgres and password protect some endpoints
This blog tracks my progress towards building a crypto oracle (tech tbd) for the price of weed. The intro post covers why, Part 1 (wip) will cover how I setup my workflows to maximize anonymity, and Part 2 follows the process of creating a bare bones Ruby on Rails app to display weed price.
This installment will walk through the steps of putting the code on the internet so the world can access it. If you don't have time to read the intro, the overall plan is:
Setup tools and anonymous accounts
Weed Price API
Make daily weed prices available over a HTTP API (we are here)
Expose over Tor
Increase update frequency and attributes tracked
Accept user input
3. Weed Price Oracle: Details TBD
HTTP API Requirements
The actual product being developed in this series is an API. It will be interacted with by other computers. But, even an API will need a user facing website to display the quotes in a friendly way, provide context, and documentation. As a reminder, quotes are the data structure we created in Part 2 to encapsulate the price of weed at a point in time.
These are the minimal requirements we will address today:
API accessible at `api.cluutch.io/v1/quotes`
Website accessible at `cluutch.io`
HTTPS enabled
Usable enough for daily price entry
Database accessible from remote machine
Lock down public ability to create/edit/delete quotes
Routing
Because we already defined `resources: :quotes` in `routes.rb`, our rails app is serving both the HTML and JSON responses at `/quotes and /quotes.json` respectively. To move the JSON response to the v1/quotes endpoint, we first create a controller for it:
➜ rails g controller v1/quotes
Then update routes.rb. This will expose quotes under the v1 namespace, but only for the subdomain api.
Eventually, we want our API to do more, but at the start we just want it to list all historical quotes. We won't worry about pagination, rate limits, authentication, or any other complication at first.
With those big pieces in place, there's only a little bit more work to copy over the JSON rendering templates from the web controller.
Testing the Subdomain
Unfortunately, even though we have correctly set the API up for the `api` subdomain, we can't immediately test it. When we access our site in development through `localhost:3000`, Rails will never detect the `api` subdomain. One option is to use a tool, like ngrok, that can create an ephemeral subdomain on request. Unfortunately, that feature is only available for the paid tier.
Instead, we can update `/etc/hosts`. With a small modification, the computer will redirect all requests to `cluutch.com` or `api.cluutch.com` to localhost. Note, that I am using `cluutch.com`, not `cluutch.io`. I do not want to spoof the address of the real website, otherwise I would not be available to access the real website from my computer once its online because my computer would try to redirect me to localhost.
Finally, we need to tell Rails to accept the corresponding hosts in development and production. Now the API is available under the api subdomain.
```
# Set as host in config/environments/development.rb
config.hosts += ["cluutch.com", "api.cluutch.com"]
# Set as host in config/environments/production.rb
config.hosts += ["cluutch.io", "api.cluutch.io"]
# Test request
✗ curl api.cluutch.com:3000/v1/quotes
[{"id":1,"date":"2021-01-19","currency":"USD","mean_price_per_oz":"210.1275","market1_price_per_oz":"315.0","market2_price_per_oz":"125.51","market3_price_per_oz":"180.0","market4_price_per_oz":"220.0","created_at":"2021-01-19T12:51:56.920Z","updated_at":"2021-01-19T12:51:56.920Z","url":"http://api.cluutch.com:3000/quotes/1.json"}]
```
Switch to Postgres
Ultimately, we are going to use Heroku to host the app. Its free and works well with Rails. Heroku does not support sqlite so migrating to Postgres is a prerequisite to launch. Moving to a true database is a good move anyways. These are the steps I took for moving from sqlite to postgres on Rails 6.1:
1. Add `pg` to Gemfile
3. Execute `bundle install`
4. Update database.yml (see Github)
6. Recreate database: `rake db:create db:migrate`
Deploy to Heroku
There are not many tools that will spin up a Rails app with a DB for free. Heroku is one of them. These are the last 7 steps needed to setup Heroku and take the API live.
1. Sign up for Heroku account
2. Create app
3. Provide name and region
5. Register heroku remote: `heroku git:remote -a cluutch-io`
6. Gotchas: I hit an issue using tzinfo-data, so I removed it. I also made SSL mandatory in that came commit.
6. Deploy: `git push heroku main`
As expected, the API is up, and only accessible over SSL.
➜ controllers git:(main) curl
api.cluutch.io/v1/quotes
➜ controllers git:(main) curl
https://api.cluutch.io/v1/quotes
[{"id":1,"date":"2021-01-23","currency":"USD","mean_price_per_oz":"282.5","market1_price_per_oz":"380.0","market2_price_per_oz":"180.0","market3_price_per_oz":"350.0","market4_price_per_oz":"220.0","created_at":"2021-01-24T05:38:14.384Z","updated_at":"2021-01-24T05:38:14.384Z","url":"
https://api.cluutch.io/quotes/1.json
"}]%
Locking Down Edit Privileges
The API only has the index page exposed. But the website still gives anybody with the URL the ability to add, edit, and remove quotes. For the moment, only I should be able to do that.
Basic HTTP Authentication is ugly but it works. In the last commit of the series, I add HTTP auth to protect the new, edit, and delete endpoints. Most of the code is in the quotes controller, by adding a filter.
After making these changes, be sure to log into Heroku and update the `HTTP_AUTH_USERNAME` and `HTTP_AUTH_PASSWORD` values with whatever you want to use.
Now when trying to access any of the restricted endpoints, you will be asked for username and password.
Closing Notes
We covered a lot already. And I already left out some details, like setting up custom domains and subdomains.
Sorry if this information and writing is unclear. I'm doing it for myself now. If it ever seems like anyone is actually reading this then I'll do better. Until then, thanks for keeping me motivated.
In the next episode, we'll be embracing our vanity and making things look pretty.