GitHub Actions CI\CD concerns for a Ruby on Rails API and React Frontend in subfolders same repo

Filipe Martins
Runtime Revolution
Published in
6 min readNov 9, 2023

--

The goal of this blog post is to quickly show how can you define your Github Actions if your Ruby on Rails application is inside of a subfolder and not accessible from the root of a repository.

This structure is something here at Runtime Revolution we don’t recommend. We always advise keeping only one application per repository. But sometimes this can be a reality or requirement for some reason and that’s why we are writing about how to solve some concerns with this state.

In this blog post, we won’t enter into details we are focusing in Concerns. For details, I advise you to check “Ruby on Rails CI\CD with Github Actions” which tries to explain all the CI\CD steps for a repository with only one application.

Just one note, this CI\CD will respect the same Git workflow from the mentioned blog post above. Any change in all branches that aren’t the production branch will trigger the CI, and all changes in the production branch main will trigger CD.

Now let’s imagine the following repository structure:

▼ project_name
ᐅ api
ᐅ frontend
- README.md

Inside the api folder, we have an API in Ruby on Rails to be consumed by the React application inside of the folder frontend.

Some concerns with this structure.

Both API and Frontend have their unit tests, if changes only occur on one side there is no reason to consume time running tests from both. The Continuous Integration (CI) should be smart to only validate what has changed.

Since each application is inside of a folder it needs us to keep in mind we need sometimes to navigate into that folder before running the required commands.

And for the Continuous Delivery (CD) part the same way, we don’t want to redistribute both applications if only one has changed. Also, you don’t want to push\send code that doesn’t make sense to exist on the cloud platform that makes your applications available on the Web.

Our CI\CD in GitHub Actions will be processed through 4 files. 2 for the API and 2 for the Frontend. Resulting in the following structure:

▼ project_name
▼ .github
▼ workflows
- api_ci_yml
- api_cd.yml
- frontend_ci.yml
- frontend_cd.yml
ᐅ api
ᐅ frontend
- README.md

CI API Side

For the API side here’s the first file, the api_ci.yml. This file contains all the Continuous Integration logic to validate the API from the folder api.

name: Setup and Validate API

# Concern 1
on:
push:
branches-ignore:
- main
paths:
- 'api/**'

jobs:
api_validation:
runs-on: ubuntu-latest
env:
RAILS_ENV: test

services:
postgres:
image: postgres:alpine
ports:
- 5432:5432
env:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

steps:
- name: Checkout repository
uses: actions/checkout@v3

# Concern 2
- name: API Setup
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.3
working-directory: api
bundler-cache: true
cache-version: 1

# Concern 3
- name: Prepare Database
working-directory: api
run: ./bin/heroku_deploy.sh

# Concern 4
- name: API Tests
working-directory: api
run: bundle exec rspec spec

# Concern 5
- name: API Upload coverage artifacts
uses: actions/upload-artifact@v3
with:
name: coverage
path: api/coverage

# Concern 6
- name: API Lint
working-directory: api
run: bundle exec rubocop

- name: API Coverage
run: |
covered_percent=$(cat api/coverage/coverage.json | jq -r '.metrics.covered_percent');
echo "Coverage ($covered_percent%)";
required_coverage=80;
echo "Minimum coverage ($required_coverage%)";
if (( $(echo "$covered_percent < $required_coverage" | bc -l) )); then
echo "Coverage ($covered_percent%) is below the required threshold of $required_coverage%."
exit 1
else
echo "Coverage ($covered_percent%) passed the required threshold of $required_coverage%."
fi

Now let’s discuss the concerns.

Concern 1

To make sure we only run these validations if the API has changed we use the parameter paths on the ruler on.

Concern 2

The official ruby setup is prepared to receive the indication that an application may not be on the root of the repository. For that, we just need to use the parameter working-directory. Since this action also handles caching, there are no concerns about where the dependencies get downloaded. They will be handled automatically if you want.

Concern 3, 4, and 6

To use our commands we need to be on the api folder. We could navigate to the folder using cd api but to make the steps cleaner we take advantage of the property working-directory .

Concern 5

If you want to make some files accessible for download don’t forget they are inside of a subfolder.

CD API Side

And here’s the second file for the API, the api_cd.yml. This file contains all the Continuous Delivery logic to distribute the API from the folder api to Heroku:

name: Distribute API to Heroku

# Concern 1
on:
push:
branches:
- main
paths:
- 'api/**'

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

# Concern 2
- name: Deploy API
uses: akhileshns/heroku-deploy@v3.12.14
with:
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_app_name: ${{secrets.HEROKU_API_APP_NAME}}
heroku_email: ${{secrets.HEROKU_EMAIL}}
appdir: "api"

Concern 1

Similar to the previous file, to make sure we only distribute our API if it has changed, we use the parameter paths on the ruler on.

Concern 2

The third-party action akhileshns/heroku-deploy is already prepared to deploy a single folder. To do that we use the parameter appdir. This will only send to Heroku the contents of the folder api.

CI Frontend Side

Now for the frontend side here’s the first file, the frontend_ci.yml. This file contains all the Continuous Integration logic to validate the Frontend from the folder frontend.

name: Setup and Validate Frontend

# Concern 1
on:
push:
branches-ignore:
- main
paths:
- 'frontend/**'

jobs:
frontend_validation:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Frontend Setup
uses: actions/setup-node@v3
with:
node-version: 18.16.1

# Concern 2
- name: Frontend Cache
id: cache
uses: actions/cache@v2
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

# Concern 3
- name: Frontend Dependencies
if: steps.cache.outputs.cache-hit != 'true'
working-directory: frontend
run: npm ci

# Concern 4
- name: Run tests
working-directory: frontend
run: npm test

# Concern 5
- name: Frontend Lint
working-directory: frontend
run: npm run lint

# Concern 6
- name: Frontend Build
working-directory: frontend
run: npm run build

Concern 1

To make sure we only run these validations if the Frontend has changed we use the parameter paths on the ruler on.

Concern 2

We want to use a cache to prevent downloading of our dependencies from external services every time we run our CI. To use it need to adjust the paths accordingly.

Concerns 3, 4, 5, and 6

To use our commands we need to be on the frontend folder. We could navigate to the folder using cd api but to make the steps cleaner we take advantage of the property working-directory .

CD Frontend Side

And finally the last file for our CI\CD process. The frontend_cd.yml. This file contains all the Continuous Delivery logic to distribute the Frontend from the folder frontend to Heroku.

name: Distribute Frontend to Heroku

# Concern 1
on:
push:
branches:
- main
paths:
- 'frontend/**'

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

# Concern 2
- name: Deploy Frontend
uses: akhileshns/heroku-deploy@v3.12.14
with:
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_app_name: ${{secrets.HEROKU_FRONTEND_APP_NAME}}
heroku_email: ${{secrets.HEROKU_EMAIL}}
appdir: "frontend"

Concern 1

Similar to the previous file, to make sure we only distribute our Frontend if it has changed, we use the parameter paths on the ruler on.

Concern 2

The third-party action akhileshns/heroku-deploy is already prepared to deploy a single folder. To do that we use the parameter appdir. This will only send to Heroku the contents of the folder frontend.

Conclusion

In this blog post, we explored and learned the concepts of Continuous Integration and Continuous Delivery for a repository with multiple applications in diferent environments. By automating the testing and deployment processes, you can ensure your application is always in a reliable state and ready for release. GitHub Actions’ flexibility and integration with your existing repositories make it an ideal choice for streamlining your development workflow.

Remember, CI/CD is not just a one-time setup; it’s an ongoing process. As your application evolves, keep iterating and refining your workflows to accommodate new requirements and improve overall efficiency. Happy coding!

--

--