# Hashnode & Astro

> Add content to your Astro project using Hashnode as a CMS

[Hashnode](https://hashnode.com/) is a hosted CMS that allows you to create a blog or publication.

## Integrating with Astro

[Section titled “Integrating with Astro”](#integrating-with-astro)

The [Hashnode Public API](https://apidocs.hashnode.com/) is a GraphQL API that allows you to interact with Hashnode. This guide uses [`graphql-request`](https://github.com/jasonkuhrt/graphql-request), a minimal GraphQL client that works well with Astro, to bring your Hashnode data into your Astro project.

### Prerequisites

[Section titled “Prerequisites”](#prerequisites)

To get started you will need to have the following:

1. **An Astro project** - If you don’t have an Astro project yet, our [Installation guide](/en/install-and-setup/) will get you up and running in no time.

2. **A Hashnode site** - You can create free personal site by visiting [Hashnode](https://hashnode.com/).

### Installing dependencies

[Section titled “Installing dependencies”](#installing-dependencies)

Install the `graphql-request` package using the package manager of your choice:

* npm

  ```shell
  npm install graphql-request
  ```

* pnpm

  ```shell
  pnpm add graphql-request
  ```

* Yarn

  ```shell
  yarn add graphql-request
  ```

## Making a blog with Astro and Hashnode

[Section titled “Making a blog with Astro and Hashnode”](#making-a-blog-with-astro-and-hashnode)

This guide uses [`graphql-request`](https://github.com/jasonkuhrt/graphql-request), a minimal GraphQL client that works well with Astro, to bring your Hashnode data into your Astro project.

### Prerequisites

[Section titled “Prerequisites”](#prerequisites-1)

1. A Hashnode Blog
2. An Astro project integrated with the [graphql-request](https://github.com/jasonkuhrt/graphql-request) package installed.

This example will create an index page that lists posts with links to dynamically-generated individual post pages.

### Fetching Data

[Section titled “Fetching Data”](#fetching-data)

1. To fetch your site’s data with the `graphql-request` package, make a `src/lib` directory and create two new files `client.ts` & `schema.ts`:

   * src/

     * lib/

       * **client.ts**
       * **schema.ts**

     * pages/

       * index.astro

   * astro.config.mjs

   * package.json

2. Initialize an API instance with the GraphQLClient using the URL from your Hashnode Website.

   src/lib/client.ts

   ```ts
   import { gql, GraphQLClient } from "graphql-request";
   import type { AllPostsData, PostData } from "./schema";


   export const getClient = () => {
     return new GraphQLClient("https://gql.hashnode.com")
   }


   const myHashnodeURL = "astroplayground.hashnode.dev";


   export const getAllPosts = async () => {
     const client = getClient();


     const allPosts = await client.request<AllPostsData>(
       gql`
         query allPosts {
           publication(host: "${myHashnodeURL}") {
             id
             title
             posts(first: 20) {
               pageInfo{
                 hasNextPage
                 endCursor
               }
               edges {
                 node {
                   id
                   author{
                     name
                     profilePicture
                   }
                   title
                   subtitle
                   brief
                   slug
                   coverImage {
                     url
                   }
                   tags {
                     name
                     slug
                   }
                   publishedAt
                   readTimeInMinutes
                 }
               }
             }
           }
         }
       `
     );


     return allPosts;
   };




   export const getPost = async (slug: string) => {
     const client = getClient();


     const data = await client.request<PostData>(
       gql`
         query postDetails($slug: String!) {
           publication(host: "${myHashnodeURL}") {
             id
             post(slug: $slug) {
               id
               author{
                 name
                 profilePicture
               }
               publishedAt
               title
               subtitle
               readTimeInMinutes
               content{
                 html
               }
               tags {
                 name
                 slug
               }
               coverImage {
                 url
               }
             }
           }
         }
       `,
       { slug: slug }
     );


     return data.publication.post;
   };
   ```

3. Configure `schema.ts` to define the shape of the data returned from the Hashnode API.

   src/lib/schema.ts

   ```ts
   import { z } from "astro/zod";


   export const PostSchema = z.object({
       id: z.string(),
       author: z.object({
           name: z.string(),
           profilePicture: z.string(),
           }),
       publishedAt: z.string(),
       title: z.string(),
       subtitle: z.string(),
       brief: z.string(),
       slug: z.string(),
       readTimeInMinutes: z.number(),
       content: z.object({
           html: z.string(),
       }),
       tags: z.array(z.object({
           name: z.string(),
           slug: z.string(),
       })),
       coverImage: z.object({
           url: z.string(),
       }),
   })


   export const AllPostsDataSchema = z.object({
       id: z.string(),
       publication: z.object({
           title: z.string(),
           posts: z.object({
               pageInfo: z.object({
                   hasNextPage: z.boolean(),
                   endCursor: z.string(),
               }),
               edges: z.array(z.object({
                   node: PostSchema,
               })),
           }),
       }),
   })


   export const PostDataSchema = z.object({
       id: z.string(),
       publication: z.object({
           title: z.string(),
           post: PostSchema,
       }),
   })


   export type Post = z.infer<typeof PostSchema>
   export type AllPostsData = z.infer<typeof AllPostsDataSchema>
   export type PostData = z.infer<typeof PostDataSchema>
   ```

### Displaying a list of posts

[Section titled “Displaying a list of posts”](#displaying-a-list-of-posts)

Fetching via `getAllPosts()` returns an array of objects containing the properties for each post such as:

* `title` - the title of the post
* `brief` - the HTML rendering of the content of the post
* `coverImage.url` - the source URL of the featured image of the post
* `slug` - the slug of the post

Use the `posts` array returned from the fetch to display a list of blog posts on the page.

src/pages/index.astro

```astro
---
import { getAllPosts } from '../lib/client';


const data = await getAllPosts();
const allPosts = data.publication.posts.edges;


---


<html lang="en">
    <head>
        <title>Astro + Hashnode</title>
    </head>
    <body>


        {
            allPosts.map((post) => (
                <div>
                    <h2>{post.node.title}</h2>
                    <p>{post.node.brief}</p>
                    <img src={post.node.coverImage.url} alt={post.node.title} />
                    <a href={`/post/${post.node.slug}`}>Read more</a>
                </div>
            ))
        }
    </body>
</html>
```

### Generating pages

[Section titled “Generating pages”](#generating-pages)

1. Create the page `src/pages/post/[slug].astro` to [dynamically generate a page](/en/guides/routing/#dynamic-routes) for each post.

   * src/

     * lib/

       * client.ts
       * schema.ts

     * pages/

       * index.astro

       * post/

         * **\[slug].astro**

   * astro.config.mjs

   * package.json

2. Import and use `getAllPosts()` and `getPost()` to fetch the data from Hashnode and generate individual page routes for each post.

   src/pages/post/\[slug].astro

   ```astro
   ---
   import { getAllPosts, getPost } from '../../lib/client';




   export async function getStaticPaths() {
     const data = await getAllPosts();
     const allPosts = data.publication.posts.edges;
     return allPosts.map((post) => {
       return {
         params: { slug: post.node.slug },
       }
     })
   }
   const { slug } = Astro.params;
   const post = await getPost(slug);


   ---
   ```

3. Create the template for each page using the properties of each `post` object. The example below shows the post title and reading time, then the full post content:

   src/pages/post/\[slug].astro

   ```astro
   ---
   import { getAllPosts, getPost } from '../../lib/client';




   export async function getStaticPaths() {
     const data = await getAllPosts();
     const allPosts = data.publication.posts.edges;
     return allPosts.map((post) => {
       return {
         params: { slug: post.node.slug },
       }
     })
   }
   const { slug } = Astro.params;
   const post = await getPost(slug);


   ---
   <!DOCTYPE html>
   <html lang="en">
       <head>
           <title>{post.title}</title>
       </head>
       <body>
           <img src={post.coverImage.url} alt={post.title} />


           <h1>{post.title}</h1>
           <p>{post.readTimeInMinutes} min read</p>


           <Fragment set:html={post.content.html} />
       </body>
   </html>
   ```

   Note

   `<Fragment />` is a built-in Astro component which allows you to avoid an unnecessary wrapper element. This can be especially useful when fetching HTML from a CMS (e.g. Hashnode or [WordPress](/en/guides/cms/wordpress/)).

### Publishing your site

[Section titled “Publishing your site”](#publishing-your-site)

To deploy your site visit our [deployment guide](/en/guides/deploy/) and follow the instructions for your preferred hosting provider.

## Community Resources

[Section titled “Community Resources”](#community-resources)

* [`astro-hashnode`](https://github.com/matthiesenxyz/astro-hashnode) on GitHub