Dev.to и GitHub REST API на моем сайте Портфолио.


Введение

Привет всем!

Это снова я, и сегодня я расскажу о том, как я использовал GitHub и Dev.to REST API для подключения на своем сайте Portfolio. Кстати, эта статья связана с моей серией статей под названием «Путешествие по сайту портфолио», и если вы еще не читали мою первую статью из этой серии, ознакомьтесь с ней, а затем посетите мой сайт портфолио.


Преимущества использования GitHub и Dev.to REST API

Ниже перечислены преимущества, которые я ощутил при использовании API. Во-первых, все, что вы измените в своем аккаунте на Dev.to и GitHub, отразится и на вашем сайте Portfolio. Во-вторых, это научит вас правильно использовать API и обрабатывать входящие данные в формате JSON, а также правильно отображать их на вашем фронтенде. Наконец, это также научит вас, как сделать некоторые кодирования бэкэнд и список продолжается. Однако этот тип функциональности может быть полезен для новичка, который хочет создать свой первый сайт-портфолио, потому что он позволяет вам получить опыт работы с Frontend и Backend вашего веб-приложения.

В этой статье блога мы будем использовать функции Next.js getStaticProps и getServerSideProps для возможностей ISR и SSR. Также я собираюсь использовать Axios для получения данных JSON. Если вы хотите узнать больше о технологиях и инструментах, которые я здесь упомянул, пожалуйста, обратитесь к документации в последней части этого блога.

Настройка экземпляра Axios и переменных Env

Есть уникальная особенность Axios, которая позволяет вам создать его экземпляр; таким образом, вы можете повторно использовать базовую конфигурацию для запроса, который имеет ту же конфигурацию при получении данных JSON. Это полезно, так как делает ваш код DRY и чистым для чтения.


Экземпляр GitHub

import axios from 'axios';

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

export default getGithub;

Вход в полноэкранный режим Выйти из полноэкранного режима

Инстанс Dev.to

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;

Войти в полноэкранный режим Выйти из полноэкранного режима

BaseURL — это базовый URL конечной точки вашего API, согласно документации GitHub это https://api.github.com, а для Dev.to https://dev.to/api.

Заголовки — это HTTP-заголовки, которые вы хотите поместить, и в моем случае я хочу поместить заголовки авторизации, чтобы GitHub не ограничивал мой запрос.


Переменные окружения в Next.js

Вы можете увидеть там process.env.NEXT_PUBLIC_GITHUB_PAT и process.env.DEV_KEY — это переменные окружения, и по определению это переменные в вашей системе, которые описывают ваше окружение.

По умолчанию Next.js отображает переменные .env только на стороне сервера, однако если вам действительно нужно отобразить переменную .env на стороне клиента, вам нужно определить переменную с помощью NEXT_PUBLIC_variableNameHere, но никогда не используйте NEXT_PUBLIC, если вы храните очень важный ключ API!

Настройка переменных Env и хранение API-ключа Dev.to и GitHub

  • Получение ключей API

Для API-ключа Github

  1. Сгенерируйте свой API-ключ GitHub PAT здесь
  2. Войдите в систему и убедитесь, что для токена PAT выбрано только разрешение public_repo или любое другое разрешение, которое вы хотите.

Для API-ключа Dev.to

  1. Сгенерируйте ключ API Dev.to здесь

После получения ключей API пришло время сохранить их в переменной .env

  • Создайте файл .env.local в корневой папке вашего проекта.
  • Внутри .env.local определите свои переменные env.
GITHUB=ApiKeyHere
DEVTO=ApiKeyHere
NEXT_PUBLIC_SOMETHINGPUBLIC=ExampleOnly
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Чтобы получить доступ к переменным env, перезапустите Next.js и получите доступ к переменным с помощью process.env.varNameHere и process.env.NEXT_PUBLIC_varNameHere.

Зачем же использовать переменную среды?
Насколько я знаю, это хорошая практика хранить любые api ключи в переменной окружения, чтобы ваш код был более DRY. И если ваш api ключ изменился, вам просто нужно перейти в файл .env.local и изменить api ключ там.

Кроме того, это не раскрывает ваши API ключи в коде, если вы не используете NEXT_PUBLIC_variableNameHere, потому что этот тип переменной env всегда будет раскрыт на стороне клиента.

Итак, теперь мы готовы подключиться к API и получить некоторые данные!


Подключение к API и выдача ответа с данными

Теперь пришло время получить наши данные с помощью getStaticProps и getServerSideProps.

Для Dev.To я собираюсь использовать getStaticProps, поскольку данные, поступающие на мой блог, не всегда меняются, а использование SSG быстрее, чем использование SSR. Для Github я собираюсь использовать SSR, так как собираюсь использовать его функцию пагинации, однако, эта тема будет в моем следующем посте, так что я надеюсь, что вы скоро его прочитаете!

API Dev.To

Предположим, мы хотим получить наши статьи, я сделаю простой компонент из этого (для простоты, я собираюсь применить простой дизайн и проигнорировать некоторые функции)

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');
    }
  }
};

Вход в полноэкранный режим Выход из полноэкранного режима

Выход:


В случае с GitHub, давайте возьмем наш профиль GitHub и отобразим его на странице Next.js (я выбрал этот вариант, потому что в следующей статье я расскажу вам, как я беру репозиторий GitHub и использую функцию пагинации API).

API GITHUB

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;
    }
  }
};

Вход в полноэкранный режим Выйти из полноэкранного режима

Вывод:

Кстати, поскольку мы выводим изображение для этого компонента, пожалуйста, убедитесь, что вы добавили домен изображения, которое вы выводите, в ваш next.config.js, для этого примера это avatars.githubusercontent.com:

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

module.exports = nextConfig;

Вход в полноэкранный режим Выйти из полноэкранного режима

Согласно документации, это необходимо, потому что это защищает наше приложение от вредоносных пользователей.

Чтобы защитить ваше приложение от злоумышленников, вы должны определить список доменов поставщиков изображений, которые вы хотите обслуживать с помощью Next.js Image Optimization API.


Поздравляем! Теперь мы подключены к нашим API! Причина, по которой это работает? Это потому, что мы использовали getStaticProps и getServerSideProps с помощью нашего экземпляра Axios.

getStaticProps запускается во время сборки, а наш экземпляр Axios получает данные из Dev.to API с помощью getStaticProps он создает статическую веб-страницу во время сборки. Это на самом деле быстрее, чем его аналог getServerSideProps, поскольку getServerSideProps всегда запускается во время выполнения (каждый запрос). Однако, единственной проблемой использования getStaticProps является то, что если есть какие-либо изменения в данных, то они не будут отображаться на нашем приложении, так как это только одноразовый запрос. Теперь, со свойством revalidate, все меняется, что делает наш getStaticProps инкрементальной статической регенерацией, так как наша страница пересматривается в течение x секунд и рендерится, имея производительность и оптимизацию статической веб-страницы.

Между тем, наш getServerSideProps работает во время выполнения (каждый запрос), поэтому наши данные всегда обновляются, и мы всегда отображаем обновленные данные. Это хорошо, если вы хотите отображать данные, которые постоянно меняются или реализовать SSR пагинацию.

Оба процесса выборки данных Next.js работают на стороне сервера, поэтому не беспокойтесь о том, чтобы поместить туда какие-либо конфиденциальные данные, потому что они никогда не будут отправлены в браузер клиента. Также именно по этой причине наш Next.js является SEO-дружественным, поскольку он создает HTML-файлы на сервере, а не на стороне клиента.

В любом случае, это мое высокоуровневое определение, а также мои знания о Next.js getStaticProps, ISR и getServerSideProps. Если вы хотите узнать больше об этих функциях, пожалуйста, обратитесь к ссылкам в последней части этой статьи.


Заключение

Подключение к Dev.to и GitHub REST API — это увлекательный опыт, поскольку я многому научился в ходе этого путешествия. Благодаря подключению к API я уверен, что все на сайте моего портфолио обновляется, что позволяет моим будущим рекрутерам видеть мои обновленные проекты и статьи. Вы также можете играть с API и получать другие данные, просто обратитесь к его документации, которая находится в разделе «Мои ссылки».

Спасибо, что прочитали мой второй пост из этой серии, я буду очень признателен за любые отзывы о моей кодовой базе. Заранее спасибо! 🙂

Я также надеюсь, что вы будете следить за моими следующими постами!


Ссылка

  • Dev.to REST API
  • GitHub REST API
  • getStaticProps
  • getServerSideProps
  • Инкрементная статическая регенерация
  • Axios
  • Переменные окружения Next.js

Оцените статью
devanswers.ru
Добавить комментарий