Using Hashnode's GraphQL API with Nextjs
Siso Ngqolosi
Published on Sunday, Feb 18, 2024
We will start by going to the Hashnode API playground visit: https://gql.hashnode.com/
To fetch your first 10 publications we use this query
query($host: String!) {
publication(host: $host) {
posts(first: 10) {
edges {
node {
coverImage {
url
}
id
publishedAt
slug
title
}
}
}
}
}
This GraphQL
query retrieves information about the first 10 posts from a publication identified by the $host
variable. It includes the cover image URL, ID, publication date, unique identifier, and title for each post.
Remember to add the variables at the bottom like
Inset your real hostname
. This you can get from the hashnode /dashboard/subdomain
{
"host": "hostname.hashnode.dev"
}
Usage in the nextjs code:
Now lets get started with our nextjs
code. This is how the code to fetch blogs will look like:
import { Post } from "@/types/posts";
import { query } from "./hashnode";
export async function geAllBlogs() {
try {
const {
data: { publication },
} = await query({
query: `
query($host: String!) {
publication(host: $host) {
posts(first: 10) {
edges {
node {
coverImage {
url
}
id
publishedAt
slug
title
}
}
}
}
}
`,
variables: {
host: process.env.NEXT_PUBLIC_HASHNODE_HOST,
},
});
const posts: Array<Post> = publication.posts.edges.map(
({ node }: { node: Post }) => node
);
return posts;
} catch (error) {
console.log(error);
}
}
Create a hashnode.ts
file. The hashnode.ts
file looks like this:
interface Query {
query: string;
variables?: object;
tags?: Array<string>;
}
export async function query({ query, variables, tags }: Query) {
const data = await fetch('https://gql.hashnode.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
next: {
tags,
},
}).then((r) => r.json());
return data;
}
This React component fetches blog posts, displays titles and cover images in a grid, and links to individual post pages.
...
export default async function Home() {
const posts = await geAllBlogs();
return (
<>
<Container className="max-w-4xl">
<h1 className="text-4xl font-bold mb-12">Blogs</h1>
<ul>
{posts?.map((post) => {
return (
<li key={post.id} className="grid sm:grid-cols-2 gap-8 mb-16">
<Link href={`/posts/${post.slug}`}>
<Image
width="600"
height="400"
className="rounded border border-zinc-200"
src={post.coverImage.url}
alt=""
/>
</Link>
<div>
<h2 className="text-2xl pb-5 border-b-2 mb-5">
<Link href={`/posts/${post.slug}`}>{post.title}</Link>
</h2>
...
This GraphQL query below, again is using a specified "host"
delves deeper into a single post identified by its "slug"
It retrieves not only cover image, ID, title, and published date, but also author details (name, profile picture, and Twitter link) and the full HTML content of the post itself. This provides a richer, more detailed view of a specific post within the publication.
query ($host: String!, $slug: String!) {
publication(host: $host) {
post(slug: $slug) {
author {
name
profilePicture
socialMediaLinks {
twitter
}
}
content {
html
}
coverImage {
url
}
id
publishedAt
title
}
}
}
To fetch the specific blog we use this code below:
This function uses Hashnode's GraphQL API to fetch a single post by its slug, returning a Post
object containing author, content, cover image, and other details.
import { query } from "@/lib/hashnode";
import { Post } from "@/types/posts";
export async function getPostBySlug(slug: string) {
const {
data: { publication },
} = await query({
query: `
query($host: String!, $slug: String!) {
publication(host: $host) {
post(slug: $slug) {
author {
name
profilePicture
socialMediaLinks {
twitter
}
}
content {
html
}
coverImage {
url
}
id
publishedAt
title
}
}
}
`,
variables: {
host: process.env.NEXT_PUBLIC_HASHNODE_HOST,
slug,
},
});
const post = publication?.post as Post;
return post;
}
Usage in nextjs
This React component below renders a single blog post based on its slug passed as a parameter. Here's a breakdown:
Fetching post: It uses
getPostBySlug
function to fetch the post details using the provided slug.Structure: The main content is wrapped in a container with columns for the post content and sidebar.
Sidebar:
- Displays the post cover image in an
aside
section.
- Displays the post cover image in an
Main content:
Displays the post title in a large heading.
Shows the author's profile picture and name with a link to their Twitter profile.
Displays the published date in a human-readable format.
Uses the
prose
library to render the HTML content of the post safely.
...
export default async function Post({ params }: PostParams) {
const post = await getPostBySlug(params.postSlug);
return (
<>
<Container className="max-w-5xl xl:max-w-7xl xl:grid xl:grid-cols-[2fr_1fr] gap-12 mt-12 mb-24">
<aside className="mb-12 xl:order-2">
<Image width="984" height="554" className="w-full rounded h-auto mb-6 xl:mb-12" src={post.coverImage.url} alt="" />
</aside>
<article className="w-full xl:order-1 mx-auto">
<h1 className="text-4xl font-bold mb-8">{ post.title }</h1>
<div className="max-w-3xl flex items-center gap-4 mb-8">
<Image width="48" height="48" className="w-12 h-auto rounded-full" src={post.author.profilePicture} alt="" />
<div>
<p className="text-lg font-bold mb-[.1rem]">{ post.author.name }</p>
<ul className="flex gap-3">
<li className="text-sm">
<a className="hover:underline hover:text-blue-500" href={post.author.socialMediaLinks.twitter}>
Twitter
</a>
</li>
</ul>
</div>
</div>
<p className="italic text-zinc-500 mb-6">
Published on
{` `}
{ new Date(post.publishedAt).toLocaleDateString('en-us', {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric'
}) }
</p>
<div
className="prose max-w-3xl prose-img:rounded"
dangerouslySetInnerHTML={{
__html: post.content.html
}}
/>
</article>
</Container>
</>
)
}
...
Remember to update your nextjs.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.hashnode.com",
},
],
},
};
module.exports = nextConfig;
I hope you found this helpful.