GitHub Repo Retriever: How I Built a Webapp to Display Any User’s Repositories

GitHub is a website and cloud-based service that helps developers store and manage their code, as well as track and manage modifications. This code is stored in a repository (usually abbreviated to “repo”). Each project has its own repo, which can be accessed via a unique URL.

The GitHub REST API is used to retrieve information that is publicly accessible, such as a user's public repositories, a user's profile, and even specifics on each repository a user has (language(s) used for that project, description, date the repo was created, etc.).

In this article, I’ll show you how I built a web app that fetches any valid user’s Github repositories with React, a Javascript library with numerous benefits such as allowing developers to create reusable components and efficiently update the user interface in response to user interactions.

This app was built as a project in my software engineering program, and this article is a walkthrough of how I implemented what was asked of me. I also added features to allow us to search for any valid user. The question is here below:

Prerequisites

To follow along smoothly, you should have a basic understanding of the following concepts

  • React and its states, hooks and routes. You can take a crash course here.

  • Importing and utilizing components in your React project.

  • Installing packages with a package manager like npm.

  • The useContext hook.

  • Tailwind CSS; which we will use to style our pages and components.

Let's get started

I started by breaking this challenge down into smaller problems and developing the solutions to each one separately. I decided to implement my solution in the following steps:

  1. Creating the project folder and installing Tailwind CSS.

  2. Creating a navigation bar, and implementing routes to all pages of the app including the 404 page.

  3. Implementing the Error Boundary.

  4. Fetch the repos and display them with pagination.

  5. Use nested routes to display details of any individual repo clicked.

  6. Implementing SEO.

Let's build the GitHub Repo Retriever step by step.

1. Setting up our project folder with and installing Tailwind CSS

I use Vite to create my React app because Vite is lightweight and has an optimized build process. You might find this documentation helpful. It taught me everything I need to know about setting up my React project with Vite.

  1. To begin, navigate to the directory you want to create your React app and then run the code npm init vite.

  2. Go ahead and enter a name for your project. This will allow you to select any front-end framework you want Vite to run. In this case, go ahead and select React.

  3. After this, select JavaScript, and Vite will create a React project for you.

  4. Navigate to the project folder and press npm install to install all dependencies.

  5. Open up the folder in your code editor and clean up the default app.

Now, let's install tailwind css in our project

  1. Go to the tailwind docs, then go to the framework installation guides and select the framework you used to build your app. Since I used Vite to create my project, I’ll click on Vite.

  2. Follow all the steps laid out for you in the docs.

  3. Test to see if Tailwind works and proceed.

2. Creating a navigation bar, and implementing routes to all pages of the app including the 404 page

The app contains a home page and a page where the repos are displayed. This requires the use of a navigation bar since I also want to be able to test the error boundary using a link in the navigation bar. To enable navigation to different pages, we use the React Router. React Router makes it possible to navigate between different pages in a React application, modifies the browser's URL to point to the appropriate page, and keeps the UI in sync with the URL.

To use React Router, install the react-router-dom to enable routing in the app

npm i react-router-dom

Now, go to the src folder and create two new folders:

  • Components: This folder contains the different components my entire app needs

  • Pages: This folder contains the different pages users can navigate to in the app. which are the Home page, Repos page and the 404 page (page that displays when no page is found)

Now, create a Navigation.jsx component in the components folder, and import this folder into the App.jsx file. The navigation component helps us navigate between each route from our UI.

Notice that I imported { Link } from react-router-dom. The Link is responsible for changing and navigating to the URL specified in it.


import { Link } from "react-router-dom";
const Navigation = () => {
  return (
    <nav className="bg-blue-300 p-2 h-8 sticky top-0 z-30 md:py-2 md:px-8">
      <ul className="flex justify-between">
        <li className="list-none">
          <Link
            to="/"
            className="no-underline text-white text-md hover:cursor-pointer hover:underline hover:decoration-double"
          >
            Home
          </Link>
        </li>
        <li>
          <Link
            to="/repos"
            className="no-underline text-white text-md hover:cursor-pointer hover:underline hover:decoration-double"
          >
            Repos
          </Link>
        </li>
        <li>
          <button
            className="border-none text-white bg-inherit text-md"
          >
            Error Boundary
          </button>
        </li>
      </ul>
    </nav>
  );
};
export default Navigation;

The directory looks like this now:

Now, import all components and pages into the App.jsx file. We also import BrowserRouter, Routes, and Route from react-router-dom. The entire App component is wrapped around BrowserRouter to enable navigation work in the entire app.

This is the App.jsx file currently:


import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Navigation from "./components/Navigation";
import Repos from "./pages/Repos";
import NotFound from "./pages/NotFound";
import Home from "./pages/Home";
import "./index.css";
function App() {
  return (
    <div className="App">
      <Router>
        <Navigation />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/repos" element={<Repos />}>
            <Route path=":repoName" />
          </Route>
          <Route path="*" element={<NotFound />} />
        </Routes>
      </Router>
    </div>
  );
}
export default App;

After doing this, we should have the routes set up, with a simple home page like this

3. Implementing Error-Boundary

React Error Boundaries are used to catch JavaScript errors or runtime errors that occur in their child component. If any error occurs while a user is using the app, it allows us to display a fallback UI so the user can understand that a problem has occurred.

To implement error boundary in the app, we start by installing the react-error-boundary package

npm i react-error-boundary

Next, go to the App.jsx file and

import { ErrorBoundary } from "react-error-boundary";

We then use this ErrorBoundary to wrap the parent element of any component we want to catch any errors in. In this case, we want to catch errors anywhere in our entire app, so we wrap the error boundary around our routes inside the App component.

We also want to be able to force an error when we click on the error boundary link in the navigation bar. To implement this, we create a function that throws an error, and then attach this function to the error boundary button, so that when its clicked, we can see the error.

So now, our app component looks like this


import { useState } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Navigation from "./components/Navigation";
import Repos from "./pages/Repos";
import NotFound from "./pages/NotFound";
import Home from "./pages/Home";
import { ErrorBoundary } from "react-error-boundary";
import Fallback from "./components/Fallback";
import "./index.css";

function App() {
    const [explode, setExplode] = useState(false);
  function Bomb() {
    throw new Error("💥 CABOOM 💥");
  } //this is the function that will run when we manually cause an error to happen
  return (
    <Router>
      <div className="App">
        <Navigation setExplode={setExplode} /> //we pass in the state of the error to the navigation component
        <ErrorBoundary
          FallbackComponent={Fallback}
          onReset={() => setExplode(false)}
          resetKeys={[explode]}
        >
          {explode ? <Bomb /> : null}
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/repos" element={<Repos />}>
              <Route path=":repoName" />
            </Route>
            <Route path="*" element={<NotFound />} />
          </Routes>
        </ErrorBoundary>
      </div>
    </Router>
  );
}
export default App;

When using the ErrorBoundary to wrap our components, we specify a FallbackComponent prop. This is the component that will be displayed in the app if the user ever runs into an error while using the app. By default, this fallback component receives the error and a function to reset the error boundary. The code below is our Fallback.jsx component:


function Fallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <h1>Something went wrong!!!</h1>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}
export default Fallback;

The Navigation.jsx component accepts setExplode state as a prop. The value of this prop is either true or false, which you can toggle whenever the Error Boundary link is clicked. Lets take a look at the Navigation.jsx component now:


import { Link } from "react-router-dom";
const Navigation = ({ setExplode }) => {
  return (
    <nav className="bg-blue-600 p-2 h-12 sticky top-0 z-30 md:py-2 md:px-8">
      <ul className="flex justify-between">
        <li className="list-none">
          <Link
            to="/"
            className="no-underline text-white text-md hover:cursor-pointer hover:underline hover:decoration-double"
          >
            Home
          </Link>
        </li>
        <li>
          <Link
            to="/repos"
            className="no-underline text-white text-md hover:cursor-pointer hover:underline hover:decoration-double"
          >
            Repos
          </Link>
        </li>
        <li>
          <button
            className="border-none text-white bg-inherit text-md"
            onClick={() => setExplode((e) => !e)}
          >
            Error Boundary
          </button>
        </li>
      </ul>
    </nav>
  );
};
export default Navigation;

Now, when you click on the error boundary button in the navigation bar, you will see the Fallback.jsx being displayed as the UI, and the error message will be displayed.

4. Fetch the repos and display them with pagination

In the app, my repositories are fetched by default, but a search bar is provided so you can search for the repos of any other user. To implement this functionality, I had to break down each functionality into components. The components to make this function up are:

  1. We create a Context to store the username variable. If you're not familiar with UseContext, please watch a youtube video here. The initial value for the username is “kenebebh” (my GitHub username) so my repos can be fetched initially. This component will be called UsernameContext.jsx. We pass the value of this context into the Repos.jsx file.

  2. A Repos.jsx file which fetches all the repos from GitHub depending on whatever username we pass into it, and returns this data. We use Axios to fetch the data. The data is returned to us as an array.

  3. The data is then passed into a Pagination.jsx file, which then maps through the array and renders a particular number of repos we specify it to render, as well as the pagination component itself.

  4. A DisplayRepo.jsx component is responsible for displaying each individual repo. We also grab the details about whatever particular repo is being clicked, and also fetch details for that repo.

5. Using nested routes to display details of any individual repo clicked

I simply adore the React functionality of nested routes after learning about all of its potential applications. Simply put, it enables us to modify a particular section of a parent route depending on the link that is clicked in this route. When the link is clicked, the link's URL changes, and the child route is shown in a given area of the entire page, alongside the parent component.

This app uses nested routing to show the specifics of any individual repository clicked. The repository we select is saved in Context (as an object). This context is then imported in the component that will extract the specific data we require, and goes ahead to display this information in an orderly manner.

6. Implementing Search Engine Optimization (SEO)

Browsers find it particularly difficult to perform SEO for React apps for a variety of reasons, but to put it simply, the crawler will only crawl up the page's empty index.html file because the app hasn't yet been injected with any JavaScript, which prevents the crawler from retrieving any useful information about the page.

To implement SEO in our app, we install react-helmet-async. React-Helmet-Async allows you to set the metadata of the page which will then be read by search engines and social media crawlers on both the server-side and the client-side.

To make your React app SEO friendly

  1. Install react-helmet-async in the app
npm i react-helmet-async
  1. Go to your index.js or main.jsx file, import { HelmetProvider } from react-helmet-async and wrap this HelmetProvider around the entire App component

  2. Go to each page in the app, and import { Helmet } from react-helmet-async. In each page, return a Helmet component, with a title and meta-description specified for the page in it (you can also specify any other valid tags e.g. script, link,noscript, and style). This title and meta-description specified in the helmet tag will then be seen in the head of the document by the crawlers.

Take a look at the code for my Home.jsx page


import { Helmet } from "react-helmet-async";

const Home = () => {
  return (
    <div className="flex justify-center text-blue-800 items-center h-3/4 flex-col gap-4">
      <Helmet>
        <title>Home Page</title>
        <meta name="description" content="Welcome to my home page"></meta>
        <link rel="canonical" href="/" />
      </Helmet>
      <h1>Welcome!!</h1>
      <h1>I am Banigo Kenebebh</h1>
    </div>
  );
};

export default Home;

Deploying my app

After I had successfully implemented all functionalities in the app, all that was left was to deploy it. Now, when you write code for your React app, you end up having multiple JavaScript files (in this scenario, I have multiple jsx files in the pages and components folder, then main.jsx file and the App.jsx file).

Because there are so many different files on the website, each one will require an additional HTTP request when a user visits, slowing down the loading time. To fix this, you can make a "build" of your app, that combines all the CSS files into one file and also all JavaScript files into a single file. By doing this, you can reduce the amount and size of files the user downloads and speed up the website.

To implement this, run the following code in your terminal:

npm run build

You will observe that a dist folder will be created in the root directory once the "build" procedure is complete. Push this code to a repository on GitHub, then go ahead and host your app on whichever platform you like. I use Netlify. Ensure that the code is hosted from the dist folder rather than the customary src folder.

Conclusion

As a newbie to React, this project was the first project I built independently. It was challenging at first; my biggest obstacle was creating the error boundary without utilizing the customary class-based approach and learning to utilize useContext to access states even in nested routes. I had a lot of fun building this app and I’m thankful for the challenge because it allowed me to better understand the majority of React's key concepts and grow to love React.

React-icons, loading states, loading spinners, and methods to catch my errors when fetching data were also used in the app, but I decided this article was long enough to not fully discuss them. I am quite open to other developers' feedback on the app and suggestions on how I can make the app better.

Hope you enjoyed reading my article.

Check out the GitHub repository for this project here.

Live link to my project here.