DEV Community

Cover image for Using Hashnode's API for Blog Previews
Corey O'Donnell
Corey O'Donnell

Posted on • Updated on • Originally published at codebycorey.com

Using Hashnode's API for Blog Previews

I recently decided to host my blog on Hashnode so I could spend more time writing and less managing the code. I still wanted to display previews and links to my most recent posts on my portfolio website. Luckily, Hashnode offers a GraphQL API where I can fetch my most recent posts.

The API

You can access the API playground and docs at api.hashnode.com. This allows you to develop your query and give you the exact response you want. After reading the docs, I built a query to give me everything I needed to display a preview on my portfolio page.

{
  user(username: "CodeByCorey") {
    publication {
      posts(page: 0) {
        slug
        title
        brief
        coverImage
        replyCount
        totalReactions
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • user(username: "CodeByCorey"): Query for my user
    • publication: Contains all information for my blog publication
    • posts(page: 0): Returns all the posts on the first page
      • slug: So I can create a link to the blog post
      • title: To display the title of my post
      • brief: Is a small snippet of the text in the post
      • coverImage: So I can show the cover image in the preview
      • replyCount: The number of comments on the post
      • totalReactions: Total number of reactions on my post

Using the Query

Now that I have the query, it's time to use it to fetch the data. I created a new lib file in my Next.js app called posts.ts. I used fetch to make the API call and passed the query to the body of the request.

const query: string = `
  {
    user(username: "CodeByCorey") {
      publication {
        posts(page: 0) {
          slug
          title
          brief
          coverImage
          replyCount
          totalReactions
        }
      }
    }
  }
`;
export const fetchPosts = async () => {
  const resp: Response = await fetch('https://api.hashnode.com', {
    method: 'POST',
    headers: {
      'Content-type': 'application/json',
    },
    body: JSON.stringify({ query }),
  })
  const hashnodeResponse = await resp.json();
  return hashnodeResponse.data.user.publication.posts;
};
Enter fullscreen mode Exit fullscreen mode

I wanted to only display the last three posts. I added another function to slice the posts to limit the response. This

export const fetchThreeMostRecentPost = async() => {
  const posts = await fetchPosts();
  return posts.slice(0, 3);
}
Enter fullscreen mode Exit fullscreen mode

Inside my container component, I used the Next.js getStaticProps function to fetch the posts and pass them to the props of my component. I added the revalidate setting to automatically regenerate my HTML when I create a new post on Hashnode.

export async function getStaticProps() {

  const posts = await fetchThreeMostRecentPosts();
  return {
    props: {
      posts
    },
    revalidate: 60
  };
}
Enter fullscreen mode Exit fullscreen mode

Now that all the data is being fetched and passed to the props, It was now time to style my components. I have been using Tailwind CSS for my portfolio website. Here is the RecentBlogPosts component:

export default function RecentBlogPosts({ posts }: Props) {
  return (
    <div className="container mx-auto py-12 text-gray-800">
      <h2 className="text-center text-2xl md:text-4xl pb-6">Recent Blog Posts</h2>
      <div className="flex flex-wrap justify-center">
        {posts.map((post, index) => (
          <a key={index} href={`https://blog.coreyodonnell.tech/${post.slug}`} className="md:w-2/3 lg:w-1/3 px-5 my-2">
            <BlogPreview post={post} />
          </a>
        ))}
      </div>
      <div className="flex flex-wrap justify-center">
        <a
          className="text-green-500 font-semibold hover:text-gray-800 py-4 px-4 rounded"
          href="https://blog.coreyodonnell.tech/"
        >
          View all posts
        </a>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

BlogPreview:

export default function BlogPreview({ post }: Props) {
  return (
    <div className="h-full border-2 border-gray-200 rounded-lg flex flex-col justify-between">
      <div className="w-full">
        <img className="lg:h-48 md:h-36 w-full object-cover object-center" src={post.coverImage} alt="blog" />
        <div className="p-6">
          <h1 className="title-font text-lg font-medium text-gray-900 mb-3">{post.title}</h1>
          <p className="leading-relaxed mb-3 text-gray-600">{post.brief}</p>
        </div>
      </div>
      <div className="flex items-center flex-wrap p-6">
        <span className="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0">
          Learn More
          <svg
            className="w-4 h-4 ml-2"
            viewBox="0 0 24 24"
            stroke="currentColor"
            strokeWidth="2"
            fill="none"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M5 12h14"></path>
            <path d="M12 5l7 7-7 7"></path>
          </svg>
        </span>
        <span className="text-gray-600 mr-3 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm pr-3 py-1 border-r-2 border-gray-300">
          <svg className="w-4 h-4 mr-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
            />
          </svg>
          {post.totalReactions}
        </span>
        <span className="text-gray-600 inline-flex items-center leading-none text-sm">
          <svg className="w-4 h-4 mr-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
            />
          </svg>
          {post.replyCount}
        </span>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

The end result after styling my components:
Blog Preview


Top comments (0)