Dev.to and GitHub REST API on my Portfolio site. CoverPicture

Dev.to and GitHub REST API on my Portfolio site.

  • #webdev
  • #beginners
  • #tutorial
  • #nextjs

Introduction

Hi everyone!

It's me again and today I'm going to talk about how I used GitHub and Dev.to REST API to connect on my Portfolio site. By the way, this blog post is related to my series called Portfolio Site Journey, and if you haven't read my first post regarding this series, kindly check it out then visit my Portfolio site


Benefits of using GitHub and Dev.to REST API

The following are the benefits that I experienced using the APIs. First, anything you change from your Dev.to and GitHub account will reflect also on your Portfolio site. Second, it teaches you how to properly consume an API and handle its incoming JSON data; and properly render it on your Frontend. Lastly, it also teaches you how to do some backend coding and the list goes on. However, this type of functionality can be beneficial to a beginner who wants to make his/her first portfolio website because it lets you experience the Frontend and Backend of your web-app.

For this blog post we're going to use Next.js getStaticProps and getServerSideProps function for ISR and SSR capabilities. Also I'm going to use Axios for fetching JSON data. If you want to know more about the technology and tools that I've mentioned here, kindly refer to the last part of this blog post for the documentation.

Setting up your Axios Instance and Env variables

There's this unique feature of Axios that lets you create an instance of it; so that you can reuse that base configuration on request that has the same config when getting JSON data. This is helpful because it makes your code DRY and clean to read.


GitHub Instance

import axios from 'axios';

const getGithub = axios.create({
  baseURL: 'https://api.github.com',
  headers: { Authorization: `Bearer ${process.env.GITHUB_PAT}` },
});

export default getGithub;

Enter fullscreen mode Exit fullscreen mode

Dev.to Instance

import axios from 'axios';

const getDev = axios.create({
  baseURL: 'https://dev.to/api',
  headers: {
    'api-key': process.env.DEV_KEY as string,
  },
});

export default getDev;

Enter fullscreen mode Exit fullscreen mode

The baseURL is the base URL endpoint of your API and according to GitHub docs its https://api.github.com and for Dev.to https://dev.to/api

The headers are the HTTP headers that you want to put in, and in my case, I want to put an Authorization headers so that GitHub doesn't limit my request.


Environment Variables in Next.js

You may see the process.env.NEXT_PUBLIC_GITHUB_PAT and process.env.DEV_KEY in there and those are Environment variables, and by definition, these are variables in your system that describe your environment.

By default Next.js only expose your .env variables on server-side however if you really need to expose a .env variable on your client-side you will need to define your variable with NEXT_PUBLIC_variableNameHere but never use NEXT_PUBLIC if you are storing a very important API key!

Setting up Env variables and Storing Dev.to and GitHub API key

  • Getting the API keys

For Github API Key

  1. Generate your GitHub PAT API key here
  2. Login and make sure only tick the public_repo permission for the PAT token or any permission you want

For Dev.to API Key

  1. Generate your Dev.to API key here

After getting the API keys its time to store it in a .env variable

  • Create a .env.local file on the root folder of your project
  • Inside the .env.local define your env variables in there
GITHUB=ApiKeyHere
DEVTO=ApiKeyHere
NEXT_PUBLIC_SOMETHINGPUBLIC=ExampleOnly
Enter fullscreen mode Exit fullscreen mode
  • To access this env variables make sure you restart your Next.js and access the variables by using process.env.varNameHere and process.env.NEXT_PUBLIC_varNameHere

So why use a Environment variable?
From my knowledge, it's a good practice to store any api keys on your env variable so that your code makes it more DRY. And if there's a change on your api key, you just need to go to your .env.local file and change the api key in there.

Also, it doesn't expose your API keys on your codebase as long as you dont use NEXT_PUBLIC_variableNameHere because this type of env variable will always be exposed on the client side.

So now we're ready to connect to the API and fetch some data in there!


Connecting with the API and Rendering the Data Response

Now, it's time to fetch our data using getStaticProps and getServerSideProps.

For Dev.To, I'm going to use getStaticProps since the data incoming on my blog post is not always changing, and using SSG is more faster than using SSR. While for Github, I'm going to use SSR since I'm going to utilize the pagination feature of it, however, that specific topic will be on my next post so I hope you'll check it out soon!

Dev.To API

Suppose we want to fetch our articles, I'll make a simple component out of it (for simplicity sake, I'm going to apply simple design and ignore some features)

import { AxiosError } from 'axios';
import type { NextPage } from 'next';
import { camelCase, isArray, transform, isObject } from 'lodash';

import getDev from '../../src/axios/getDev';

type CamelizeInput = Record<string, unknown> | Array<Record<string, unknown>>;

const camelize = (obj: CamelizeInput) =>
  transform(
    obj,
    (result: Record<string, unknown>, value: unknown, key: string, target) => {
      const camelKey = isArray(target) ? key : camelCase(key);
      // eslint-disable-next-line no-param-reassign
      result[camelKey] = isObject(value)
        ? camelize(value as Record<string, unknown>)
        : value;
    }
  );

interface DevArticlesParent {
  title: string;
  pageViewsCount: number;
  description: string;
  positiveReactionsCount: number;
  readingTimeMinutes: number;
}

interface DevArticlesType extends Required<DevArticlesParent> {
  id: number;
  tagList: string[];
  commentsCount: number;
  slug: string;
}

interface JSONResDataType {
  articles: DevArticlesType[];
}

interface JSONResArticle {
  data: Record<string, DevArticlesType[]>;
}

const Portfolio: NextPage<JSONResDataType> = ({
  articles,
}: JSONResDataType) => {
  console.log(articles);
  return (
    <section
      style={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
        gap: 10,
        width: '100%',
      }}
      aria-label="Article Page Section"
    >
      <h1
        style={{
          fontSize: 24,
          marginBottom: 24,
          fontWeight: 'bold',
          textDecoration: 'underline',
        }}
      >
        My Articles
      </h1>
      <ul
        style={{
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          alignItems: 'center',
          gap: 10,
        }}
      >
        {articles.map((art) => (
          <li style={{ border: '1px solid black', width: '100%' }} key={art.id}>
            <h1 style={{ fontSize: 24 }}>{art.title}</h1>
            <p>{art.description}</p>
            <span>{art.pageViewsCount} Views</span>
            <br />
            <span>{art.positiveReactionsCount} Reacts</span>
          </li>
        ))}
      </ul>
    </section>
  );
};

export default Portfolio;

export const getStaticProps = async () => {
  try {
    const resArticles: JSONResArticle = await getDev('/articles/me/published');
    const resArticlesData = resArticles.data;
    const camelizeArticles = camelize(
      resArticlesData
    ) as unknown as DevArticlesType[];

    return {
      props: {
        articles: camelizeArticles,
      },
      // Next.js ISR feature that revalidates the Static page for
      // every 5 seconds
      // Revalidate every 5 seconds
      revalidate: 5,
    };
  } catch (error) {
    const err = error as AxiosError;

    if (err.response) {
      // The request was made and the server responded with a
      // status code that falls out of the range of 2xx

      if (err.response.status === 404) {
        return {
          notFound: true,
        };
      }

      throw new Error(
        `Something went wrong on fetching my blog articles Status 
         code: ${err.response.status}`
      );
    } else if (err.request) {
      // The request was made but no response was received
      // `err.request` is an instance of XMLHttpRequest in the
      // browser and an instance of
      // http.ClientRequest in node.js

      throw new Error('Request was made but no response data received.');
    } else {
      // Something happened in setting up the request that
      // triggered an err

      throw new Error('Something went wrong on fetching my blog articles');
    }
  }
};

Enter fullscreen mode Exit fullscreen mode

Output:
ArticlesOutput


While for GitHub, let's fetch our GitHub Profile and render it into our Next.js Page (I personally picked this because on my next article, I will share to you how I fetch my GitHub Repository and utilize the Pagination feature of the API)

GITHUB API

import { AxiosError } from 'axios';
import type { GetServerSideProps, NextPage } from 'next';
import { camelCase, isArray, transform, isObject } from 'lodash';
import Image from 'next/image';

import getGithub from '../../src/axios/getGithub';

type CamelizeInput = Record<string, unknown> | Array<Record<string, unknown>>;

const camelize = (obj: CamelizeInput) =>
  transform(
    obj,
    (result: Record<string, unknown>, value: unknown, key: string, target) => {
      const camelKey = isArray(target) ? key : camelCase(key);
      // eslint-disable-next-line no-param-reassign
      result[camelKey] = isObject(value)
        ? camelize(value as Record<string, unknown>)
        : value;
    }
  );

interface UserDataValues {
  name: string;
  login: string;
  avatarUrl: string;
  location: string;
  hireable: boolean;
  bio: string;
  publicRepos: number;
  ownedPrivateRepos: number;
}

interface PortfolioProps {
  userData: UserDataValues;
}

interface JSONResUser {
  data: UserDataValues;
}

const Portfolio: NextPage<PortfolioProps> = ({ userData }: PortfolioProps) => {
  const { avatarUrl, name, login, bio, location, hireable, publicRepos } =
    userData;

  return (
    <section
      style={{
        display: 'flex',
        flexDirection: 'column',
        width: '100%',
        justifyContent: 'center',
        alignItems: 'center',
        textAlign: 'left',
      }}
      aria-label="User Profile Card"
    >
      <div style={{ border: '1px solid black', padding: 60 }}>
        <div
          style={{
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          <Image
            style={{ borderRadius: 9999 }}
            src={avatarUrl}
            width={300}
            height={300}
            alt="Profile Picture"
          />
        </div>
        <h1 style={{ fontSize: 32, fontWeight: 'bold' }}>{name}</h1>
        <h2 style={{ fontSize: 24, fontWeight: 'bold' }}>{login}</h2>
        <h3 style={{ fontSize: 18, fontWeight: 'bold' }}>
          <span>{location}</span>
        </h3>
        <p>{bio}</p>

        <p>
          Looking for job:{' '}
          <span>{hireable ? 'Yes' : 'No, currently busy'}</span>
        </p>

        <p>
          Total Repos:
          <span>{publicRepos}</span>
        </p>
      </div>
    </section>
  );
};

export default Portfolio;

export const getServerSideProps: GetServerSideProps = async () => {
  try {
    // User Data
    const responseUser: JSONResUser = await getGithub('/user');
    const userData = responseUser.data;
    const camelizeUserData = camelize(
      userData as unknown as Record<string, unknown>
    );

    return {
      props: {
        userData: camelizeUserData as unknown as UserDataValues,
      },
    };
  } catch (error) {
    const err = error as AxiosError;
    const errorMessage = new Error('Something went wrong, please try again');

    if (err.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx

      if (err.response.status === 404) {
        return {
          notFound: true,
        };
      }

      if (err.response.status === 403) {
        throw new Error('Forbidden Access!');
      }

      throw errorMessage;
    } else if (err.request) {
      // The request was made but no response was received
      // `err.request` is an instance of XMLHttpRequest in the browser and an instance of
      // http.ClientRequest in node.js

      throw errorMessage;
    } else {
      // Something happened in setting up the request that triggered an err

      throw errorMessage;
    }
  }
};

Enter fullscreen mode Exit fullscreen mode

Output:

GitHubOutput

By the way since we're rendering an image for this component please make sure you add the domain of the image you are rendering on your next.config.js for this example its avatars.githubusercontent.com:

const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ['avatars.githubusercontent.com'],
  },
};

module.exports = nextConfig;

Enter fullscreen mode Exit fullscreen mode

As per the documentation we need to this is because it protects our app for malicious users.

To protect your application from malicious users, you must define a list of image provider domains that you want to be served from the Next.js Image Optimization API.


And congratulations! We're now connected to our APIs! The reason why it works? It's because we used getStaticProps and getServerSideProps with the help of our Axios instance.

getStaticProps runs during build-time, and our Axios instance fetches the data from Dev.to API with getStaticProps it makes a static web page during build-time. It is actually faster than its counterpart getServerSideProps since getServerSideProps always run on run-time(Every Request). However, the only problem of using getStaticProps is, if there's any changes on the data because it will not render on our app as it's only a one-time request. Now, with the revalidate property, everything changes which makes our getStaticProps an Incremental Static Regeneration for the reason our page revalidates for x amount of seconds and renders it while having the performance and optimization of a static webpage.

Meanwhile, our getServerSideProps runs on run-time(Every Request) so our data is always updated, and we always render an updated data. This is good if you want to render a data that is always changing or implement a SSR pagination.

Both Next.js data fetching runs on server-side code so don't worry about putting any sensitive data on it, because it will never ship on the client's browser. Also this is the reason why our Next.js is SEO-friendly since it builds the HTML files on the server rather than on the client side.

Anyway, this is my high-level definition and also from my knowledge on Next.js getStaticProps, ISR and getServerSideProps. If you want to know more about these features, please refer to the last part of this article for the references.


Conclusion

Connecting to Dev.to and GitHub REST API is a fun experience since I've learned a lot during the course of this journey. With this connection to the API, I'm confident enough that everything on my portfolio site is updated, thus making my future recruiters see my updated Projects and Articles. You can also play around with the API and fetch other data, just refer to its documentation which is located on my Reference part.

Thank you for reading my second post of the series, I would really appreciate any feedback on my codebase. Thanks in advance! 🙂

I also hope you'll tune in to my next posts!


Reference