Automated Portfolio Website using GitHub API

Automated Portfolio Website using GitHub API

In this article, we'll be discussing a project I recently worked on where I built a portfolio website that pulls data from my GitHub account using the GitHub API. The website displays a list of all of my repositories, with the added feature of pagination, as well as a page dedicated to displaying the data of each single repository. The project was built using React js, and I also incorporated SEO, Error Boundary, and a 404 page.

The goal of this project was to create a simple portfolio website that showcases my work as a developer. By leveraging the power of the GitHub API, I was able to automate the process of displaying my repositories, making it easy for potential employers or collaborators to view my portfolio. Additionally, the project features pagination, which makes it easy to navigate through a large number of repositories, and nested routes which makes it easy to navigate to a specific repository.

In this article, I'll be walking you through the technical details of how I built this project, including the technologies and frameworks I used, the specific features I implemented, and any challenges I faced along the way. I'll also be providing code samples and screenshots to help illustrate the different aspects of the project. So, let's dive in!

Technologies Used

  • React js

  • GitHub API

React js

React is a JavaScript library for building user interfaces, React is used to build single-page applications. React allows us to create reusable UI components.

An extensive explanation of React can be found here.

GitHub API

An application program interface (API) is a set of routines, protocols, and tools for building software applications. An API specifies how software components should interact. Additionally, APIs are used when programming graphical user interface (GUI) components.

The GitHub API was used for this project. It contains all the necessary details and endpoints required for our portfolio website.

The GitHub API documentation can be found here

Creating a React App

Creating a React app can seem daunting at first, but with the right tools and some basic knowledge of the framework, it can be a relatively straightforward process.

The first step in creating a React app is to make sure that you have Node.js and npm (Node Package Manager) installed on your computer. These tools are necessary for setting up and managing the dependencies of your app.

Once you have Node and npm installed, you can use the "create-react-app" command to set up a new React project. This command is a tool built by Facebook to simplify the initial setup of a React app. To use it, simply open your command line interface(CLI) and type:

npx create-react-app my-app

This will create a new directory called "my-app" with all the necessary files and directories for a basic React app. You can then navigate into the new directory and start the development server with the following command:

cd my-app
npm start

This will run your app in development mode on localhost:3000 , open the URL in your browser.

from here, you can start building your app by editing the files in the "src" directory. The main entry point for your app is "src/index.js", and the main component that gets rendered on the page is "src/App.js". You can start editing these files to build your custom app.

Below is what we would be building in our GitHub portfolio app;

  • Fetching Github profile details from GitHub API.

  • A paginated page that shows all GitHub Repositories of the profile.

  • A page that shows the details of a single repository using nested routing.

  • Error Boundary.

  • 404 error page.

  • Contact Form.

We'd also be making use of these libraries to achieve our project;

React-Router-Library: This library enables navigation to different pages across the app. An extensive example can be found here.

React-Hook-Form: It involves handling all the input states and their changes and validating that input when the form gets submitted. An extensive example can be found here.

React-Error Boundary: Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

React Icons: These contain pre-designed icons that can be imported into your project.

To install all these libraries at once, type the following code in your CLI

npm install react-router-dom react-error-boundary react-icons react-hook-form

check package.json to ensure all libraries have been installed.

Once that is confirmed, we can proceed to building our website.

Firstly we'd be implementing routing:

first step;

import { BrowserRouter as Router,  Routes, Route } from "react-router-dom";

the index.js file should look like this

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

We'd be having 7 pages hereby having the following routes in our App.js

 <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="repositories" element={<Repositories />} />
        <Route path="repositories/:repoName" element={<RepoDetails />} />
        <Route path="*" element={<Error404 />} />
        <Route path="errortest" element={<ErrorTest />} />
        <Route path="errorpage" element={<ErrorPage />} />


      </Routes>

All created pages in our app are already linked and can be accessed.

next we'd be implementing Error boundaries in our application;

the first step involves;

importing the error-boundary component to the application,

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

Create a function that will be passed as the FallbackComponent to the imported component.

// This is our ErrorFallback component
function ErrorFallback({error, resetErrorBoundary}) {
  // We can render any custom fallback UI
 return (
   <div className='fallback' role="alert">
     <p>Something went wrong:</p>
     <pre style={{color: 'red'}}>{error.message}</pre>
     <button onClick={resetErrorBoundary}>Try again</button>
   </div>
 )
}

Then we wrap our app in the ErrorBoundary component;

import {ErrorBoundary} from 'react-error-boundary'
export default function App() {
  return (
      <div className="App">
        <Router>
      </ErrorBoundary>
        <ErrorBoundary>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="repositories" element={<Repositories />} />
        <Route path="repositories/:repoName" element={<RepoDetails />} />
        <Route path="*" element={<Error404 />} />
        <Route path="errortest" element={<ErrorTest />} />
        <Route path="errorpage" element={<ErrorPage />} />


      </Routes>
    </ErrorBoundary>
    </Router>


      </div>
  );
}

The app.js file should look like this now;

import { BrowserRouter as Router,  Routes, Route } from "react-router-dom";
import {ErrorBoundary} from 'react-error-boundary'
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";

import Navbar from "./components/Navbar";
import RepoDetails from "./components/RepoDetails";
import Repositories from "./components/Repositories";
import Error404 from "./components/Error404";
import ErrorPage from "./components/ErrorPage";

import "./App.css";
import ErrorTest from "./components/ErrorTest";


export default function App() {
  return (

      <div className="App">
        <Router>
          <ErrorBoundary >
      <Navbar />
      </ErrorBoundary>
        <ErrorBoundary>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="repositories" element={<Repositories />} />
        <Route path="repositories/:repoName" element={<RepoDetails />} />
        <Route path="*" element={<Error404 />} />
        <Route path="errortest" element={<ErrorTest />} />
        <Route path="errorpage" element={<ErrorPage />} />


      </Routes>
    </ErrorBoundary>
    </Router>


      </div>

  );
}

Using ContextAPI to pass data from GitHub API across the application.

Context provides a way to pass data through the component tree without having to pass down manually at every level.

steps involved include;

first step

import { useContext, createContext } from "react";

the logic contained in the context is contained in the code below;

// Create a context for the data
export const GithubContext = createContext();

// Create a provider for the data
export const GitHubProvider = ({ children }) => {


  // Create a state for the data
  const [userName] = useState("kodeman2");
  const [gitHubRepos, setGitHubRepos] = useState([]);
  const [Loading, setLoading] = useState(false);

  let baseUrl = `https://api.github.com/users/${userName}/repos?per_page=100`; // base url for github api

  // Fetch data from github api
  useEffect(() => {
    setLoading(true);
    axios.get(baseUrl).then((response) => {
      const myRepos = response.data;

      setLoading(false);
      // Set the data to the state
      const filteredRepos = myRepos?.filter((repo) => repo);
      setGitHubRepos(filteredRepos);
    });
  }, [userName]);

  // Return the data and loading state
  return (
    // Pass the data and loading state to the children
    <GithubContext.Provider value={{ gitHubRepos }}>
      {Loading ? <Loadingspinner /> : children}
    </GithubContext.Provider>
  );
};

// Create a custom hook to use the data
export function useGithubRepos() {
  const context = useContext(GithubContext);
  if (!context) {
    throw new Error("useGithubRepos must be used within a GithubProvider");
  }

  // Return the data and loading state
  return context;
}

wrap the whole application with the context component

import { BrowserRouter as Router,  Routes, Route } from "react-router-dom";
import {ErrorBoundary} from 'react-error-boundary'
import { GitHubProvider } from "./components/GITHUBCONTEXT";
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";

import Navbar from "./components/Navbar";
import RepoDetails from "./components/RepoDetails";
import Repositories from "./components/Repositories";
import Error404 from "./components/Error404";
import ErrorPage from "./components/ErrorPage";

import "./App.css";
import ErrorTest from "./components/ErrorTest";


export default function App() {
  return (
    <GitHubProvider>
      <div className="App">
        <Router>
          <ErrorBoundary >
      <Navbar />
      </ErrorBoundary>
        <ErrorBoundary>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="repositories" element={<Repositories />} />
        <Route path="repositories/:repoName" element={<RepoDetails />} />
        <Route path="*" element={<Error404 />} />
        <Route path="errortest" element={<ErrorTest />} />
        <Route path="errorpage" element={<ErrorPage />} />


      </Routes>
    </ErrorBoundary>
    </Router>


      </div>

      </GitHubProvider>
  );
}

to access the data across the app...

import { useGithubRepos } from "./GITHUBCONTEXT";

code below shows how the data fetched is used in the repositories.jsx file;

import { Link } from "react-router-dom";
;
import { useGithubRepos } from "./GITHUBCONTEXT";



export default function Repositories() {



  const { gitHubRepos } = useGithubRepos();




  const [userName] = useState('kodeman2');

  useEffect(() => {


  const totalRepos = gitHubRepos?.length;
  setTotalPages(Math.ceil(totalRepos / REPOS_PER_PAGE)); 


  }, [gitHubRepos]);

  const indexOfLastRepo = page * REPOS_PER_PAGE;
  const indexOfFirstRepo = indexOfLastRepo - REPOS_PER_PAGE;
  const currentRepos = gitHubRepos?.slice(indexOfFirstRepo, indexOfLastRepo);

  const handleClick = (number) => {
    setPage(number);
  };

  return (
    <div className="repositories">
     <div className="repoleft">
     <h1>{userName}'s Repositories</h1>
      <Pagination
        totalPages={totalPages}
        handleClick={handleClick} 
        Page={page}
      />
      <p>pages - {page}</p>
      <div className="repositories__container">
        {currentRepos?.map(({ name, id, topics, watchers, description }) => (

          <Link to={`/repositories/${name}`} key={id} className="rep_name">
            <div className="rep_container">
              <h2>{name}</h2>

              <p>{topics}</p>
              <p className="Forks">watchers - {watchers}</p>


              <p>{description}</p>

            </div>
          </Link>
        ))}
      </div>  
     </div>

  );
}

Implementing Pagination

Pagination is used to break up a block of content into navigable pages on a webpage.

Aside from making it easier to see information, it also helps when loading data from a server. Using pagination, you can decide to only load a fixed number of items each time when the user decides to see them. This helps save time and avoid information overload on a page.

In this project, we are going to create a pagination component that can be reused all over the application.

We would receive three props for now from the App.js component (parent component) in the pagination component: postsPerPage , page and handleClick. These data will assist us in determining the total number of pages for our content.

const Pagination = ({totalPages, handleClick, Page})=> {
 const pages = [...Array(totalPages).keys()].map((numbers) => numbers + 1);

 return (

   <div className="pagination">
    <button
    onClick={() => handleClick(Page - 1)}
    disabled={Page === 1}
    >
    Prev
    </button>
    {pages.map((number) => (
      <button
      key={number}
      onClick={() => handleClick(number)}
      className={`${Page === number && "active"}`}
    >
      {number}
    </button>
    ))}
    <button
    onClick={() => handleClick(Page + 1)}
    disabled={Page === totalPages}
    >
    Next
    </button>
   </div>
 );
};

export default Pagination;

now we'd make use of the pagination component in our App(the listed Repositories Page containing data fetched from the Github API).

import { Link } from "react-router-dom";
import { useState, useEffect } from "react";

import Pagination from "./Pagination";
//repositories per page (constant with a value of 5)
import {REPOS_PER_PAGE} from "../Constants";
import { useGithubRepos } from "./GITHUBCONTEXT";



export default function Repositories() {



  const { gitHubRepos } = useGithubRepos();
  const [page, setPage] = useState(1);
  const [totalPages, setTotalPages] = useState(0);



  const [userName] = useState('kodeman2');

  useEffect(() => {


  const totalRepos = gitHubRepos?.length;
  setTotalPages(Math.ceil(totalRepos / REPOS_PER_PAGE)); 


  }, [gitHubRepos]);

  const indexOfLastRepo = page * REPOS_PER_PAGE;
  const indexOfFirstRepo = indexOfLastRepo - REPOS_PER_PAGE;
  const currentRepos = gitHubRepos?.slice(indexOfFirstRepo, indexOfLastRepo);

  const handleClick = (number) => {
    setPage(number);
  };

  return (
    <div className="repositories">
     <div className="repoleft">
     <h1>{userName}'s Repositories</h1>

//pagination component
      <Pagination
        totalPages={totalPages}
        handleClick={handleClick} 
        Page={page}
      />
      <p>pages - {page}</p>
      <div className="repositories__container">
        {currentRepos?.map(({ name, id, topics, watchers, description }) => (

          <Link to={`/repositories/${name}`} key={id} className="rep_name">
            <div className="rep_container">
              <h2>{name}</h2>

              <p>{topics}</p>
              <p className="Forks">watchers - {watchers}</p>


              <p>{description}</p>

            </div>
          </Link>
        ))}
      </div>  
     </div>
     <div className="reporight">
      <img  src="../assets/robo-wave.png" alt="robo-wave" />

      </div>

    </div>
  );
}

implementing React-Hook-form for the contact form

The react-hook-form library provides a useForm hook which we can use to work with forms.


import { useForm } from "react-hook-form";
export default function Contact() {
  const { register, handleSubmit, errors } = useForm(); // initialize the hook
  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <div className="contact">
     <div className="rightcontact">
      <div className="contact_form">
        <h1>Get In Touch</h1>
        <form onSubmit={handleSubmit(onSubmit)} className="form">
          <label htmlFor="name">Name
          </label>
            <input type="text" name="name" id="name" placeholder="Your Name" />
          <label htmlFor="email">Email
          </label>
          <input type="email" name="email" id="email" placeholder="Your Email" />
          <label htmlFor="message">Message
          </label>
          <textarea name="message" id="message" cols="30" rows="10" placeholder="Your Message"></textarea>
          <button type="submit">Send Message <AiOutlineArrowRight/></button>         
          </form>
</div>
      </div>
      </div>
  );
}

the final App.js file looks like this;

import { BrowserRouter as Router,  Routes, Route } from "react-router-dom";
import {ErrorBoundary} from 'react-error-boundary'
import { GitHubProvider } from "./components/GITHUBCONTEXT";
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";

import Navbar from "./components/Navbar";
import RepoDetails from "./components/RepoDetails";
import Repositories from "./components/Repositories";
import Error404 from "./components/Error404";
import ErrorPage from "./components/ErrorPage";

import "./App.css";
import ErrorTest from "./components/ErrorTest";


export default function App() {
  return (
    <GitHubProvider>
      <div className="App">
        <Router>
          <ErrorBoundary >
      <Navbar />
      </ErrorBoundary>
        <ErrorBoundary>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="repositories" element={<Repositories />} />
        <Route path="repositories/:repoName" element={<RepoDetails />} />
        <Route path="*" element={<Error404 />} />
        <Route path="errortest" element={<ErrorTest />} />
        <Route path="errorpage" element={<ErrorPage />} />


      </Routes>
    </ErrorBoundary>
    </Router>


      </div>

      </GitHubProvider>
  );
}

Conclusion

In conclusion, the project we discussed in this article was a portfolio website that pulled data from a GitHub account using the GitHub API. The website displayed a list of all of the repositories, along with pagination, a page dedicated to displaying the data of each single repository, and featured additional functionality such as SEO, Error Boundary and a 404 page. The project was built using React js, a popular JavaScript library for building user interfaces.

The Source code for this project: https://github.com/kodeman2/Altschool-github-api

Live-Link: https://kodeman-githubportfolio.netlify.app/contact

Thanks for Reading!!!