---
title: "Step-by-step guide to building a blog with Angular and Hashnode GraphQL APIs"
description: "Hashnode GraphQL API gives you countless possibilities to build your blog using your toolbox–a fronted kit. You can access information such as blog posts…"
author: "Ozan Akman"
published: "2024-02-16"
updated: "2024-02-19"
url: "https://hashnode.com/blog/step-by-step-guide-to-building-a-blog-with-angular-and-hashnode-graphql-apis"
tags: ["JavaScript","Programming Blogs","Beginner Developers","Angular"]
---

# Step-by-step guide to building a blog with Angular and Hashnode GraphQL APIs

## Introduction

Hashnode GraphQL API gives you countless possibilities to build your blog using your toolbox–a fronted kit. You can access information such as blog posts, drafts, comments, tags, and many more. We’re going to explore [Hashnode API](https://apidocs.hashnode.com) by building the Angular front end. Before delving deeper, you can also take a look at [Headless Hashnode](https://github.com/hashnode/starter-kit), an official blog starter kit, built with Next.js and Tailwind. You can start your blog, your way. Deploy on any domain.

## Step 1: Creating a new Angular project

With `node.js` and `npm` installed, please run the following command to install Angular CLI, tooling for Angular development.

```bash
npm install -g @angular/cli
```

The Angular CLI tool has a built-in command to create a new project. It will install the needed packages and set up the initial project. Also, it will ask some project-related questions. We will answer them accordingly:

```plaintext
ng new my-blog

? Which stylesheet format would you like to use? CSS
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? Yes
```

Now, we are ready to serve our project using the CLI command `ng serve`:

```bash
cd my-blog/ && ng serve

Application bundle generation complete. [1.651 seconds]
Watch mode enabled. Watching for file changes...
  ➜  Local:   http://localhost:4200/
  ➜  press h + enter to show help
```

Hot Reloading is enabled by default. Any changes will have an immediate effect on the browser window. Navigate to [http://localhost:4200/](http://localhost:4200/)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707641657777/dca7f722-ae9e-4d8d-b114-6424e2ac4cb6.png align="left")

### Step 1.2: Setting up prerequisites

Angular has built-in `HttpClient` that we can use for fetching posts from Hashnode GraphQL API. You can navigate [Hashnode Public API Docs](https://apidocs.hashnode.com/) to check out the existing examples. There is also [Hashnode GraphQL API Playground](https://gql.hashnode.com) where you can test your queries before implementing them.

Before using `HttpClient`, we need to define `HttpClientModule` as a root dependency in our project. This allows us to inject `HttpClient` as a dependency in any component. We will be able to make HTTP requests to API endpoints. To set up `HttpClientModule`, we will use the `importProvidersFrom` method from Angular. We need to do the following:

1. Open `src/app/app.config.ts`
    
2. Add the `HttpClientModule` from `@angular/common/http`
    
3. Wrap it into `importProvidersFrom` call
    

```typescript
import {ApplicationConfig, importProvidersFrom} from '@angular/core';
import {HttpClientModule} from '@angular/common/http';

export const appConfig: ApplicationConfig = {  
  providers: [  
    // ... other providers
    importProvidersFrom(HttpClientModule), 
  ]};
```

### Step 1.3: Styling

Angular has a default template located in `src/app/app.component.html`. That’s the template we have seen when we browsed [localhost](http://localhost). You can remove everything in this file until the last line, the `router-outlet` tag. That element will be useful when we use the router component to show different components per URL. The `app.component.html` should look like this after the cleanup:

```html
<router-outlet />
```

Now we can set up our blog template. Let’s install Tailwind for that!

### Step 1.4: Setting up Tailwind

Install Tailwind and `@tailwindcss/typograph` package, using `npm` and `npx`,

```bash
npm install -D tailwindcss @tailwindcss/typography
npx tailwindcss init
```

Next, we also need to set up the Tailwind config file, `tailwind.config.js`:

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}
```

Next, we can add the base Tailwind styles in the `src/styles.css` file:

```css
@tailwind base;  
@tailwind components;  
@tailwind utilities;
```

We’re ready to use Tailwind. Now, the `ng serve` command will build Tailwind styles too! We can already wrap our app into a container by adding the following in `app.component.html`:

```html
<div class="container mx-auto px-4 w-1/2 py-8">
  <router-outlet></router-outlet>  
</div>
```

That’s it! We will continue with styling later when we fetch the blog posts.

## Step 2: Listing blog posts

In this tutorial, we will focus on two different components: `ListComponent` and `PostComponent`. As their name already suggests, the `ListComponent` will focus on listing the blog posts, and we will show it in our home page. On the other hand, the `PostComponent` will focus on showing a single blog post. We will configure a different route to handle that.

### Step 2.1: Creating a new component

Angular has a built-in CLI command to create components easily.

```bash
ng generate component <component-name>
```

In our case, we will start by the `List` component:

```bash
ng generate component list
```

By default, this command creates the following:

* A directory named after the component
    
* A component file, `list.component.ts`
    
    * A TypeScript class that defines behavior
        
* A template file, `list.component.html`
    
    * An HTML template that declares what renders on the page
        
* A CSS file, `list.component.css`
    
    * Since we are using Tailwind, we don’t need this CSS file. You can safely remove it. You also need to remove it from the `@Component` decorator. It's mentioned in the `styleUrl` property.
        

You can also manually create components using `@Component` decorator. If you wish to do that, please create a new file, `list/list.component.ts`

```typescript
import { Component } from '@angular/core';  

@Component({  
  selector: 'app-list',  
  standalone: true,  
  templateUrl: './list.component.html', 
})  
export class ListComponent {  
  
}
```

We need to import `CommonModule` in our component to use directives like `ngForOf`, and `ngIf`, in our templates. The `ngForOf` directive will allow us to loop through the blog posts. The final version of the `ListComponent` should look like this:

```typescript
import {CommonModule} from "@angular/common";

@Component({  
  selector: 'app-list',  
  standalone: true,  
  imports: [CommonModule],  
  templateUrl: './list.component.html',  
})

export class ListComponent { 

}
```

#### Configuring the `ListComponent` to act as the homepage

Let’s make this `ListComponent` the default page. We want to see this component when we navigate to [http://localhost:4200/](http://localhost:4200/). This is quite easy to do with `app.routes.ts`. Open that file and add the following lines:

```typescript
import { Routes } from '@angular/router';
import {ListComponent} from "./list/list.component";

export const routes: Routes = [
  {path: '', component: ListComponent},
];
```

### Step 2.2: Creating the `BlogPost` type

First, let’s create our `BlogPost` type and a variable to store an array of blog posts. This type could be used in different places, so it’s better to define it outside a component. Create a file called `types.ts` under `src/app/` location.

```typescript
export type BlogPost = {
  slug: string,
  title: string,
  brief: string
  coverImage: {
    url: string,
  },
}
```

We can import this type in our `list.component.ts` and initialize our array.

```typescript
import {BlogPost} from "../types";

// @Component(...)
export class ListComponent {  
  blogPosts: BlogPost[] = [];  
}
```

Next up, we need to send the API request to Hashnode GraphQL API.

### Step 2.3: Injecting `HttpClient`

We can import the `HttpClient` from `@angular/common/http`, and Angular’s dependency injection container will resolve it automatically. All we need to do is add it as a constructor dependency:

```typescript
import {HttpClient} from '@angular/common/http';

export class ListComponent {
  constructor(private httpClient: HttpClient) {  

  }
}
```

Now, we’re ready to make requests to Hashnode GraphQL API.

### Step 2.4: Fetching posts from Hashnode GraphQL API

In this section, we’ll use the recently injected `HttpClient`, and the `BlogPost` type we just created. Let’s define our GraphQL query in `list.component.ts`:

```javascript
// imports

const postsQuery = `
query {
  publication(host: "compiler.blog") {
    title
    posts(first: 10) {
      edges {
        node {
          slug
          title
          brief
          publishedAt
          coverImage {
            url
            photographer
          }
        }
      }
    }
  }
}
`;

// @Component(...)
```

Let’s create a corresponding type to this request so we can map the response output and use it in a type-safe way.

```typescript
// const postsQuery = ...

type ApiResponse = {
  data: {
    publication: {
      posts: {
        edges: [
          {
            node: BlogPost
          }
        ]
      }
    }
  }
}

// @Component(...)
```

For the simplicity, we will define these types and queries inside the component. Of course, for more complicated examples, you could define a service such as `HashnodeGraphqlService` or `PostFetchService`, and encapsulate the API request/response inside that service. For now, we will use the `OnInit` interface to achieve that inside our component.

We will bind response data to variables during the initialization of our component. Later, we will use this blog post data in our template. Let’s glue everything together and make our first request! Here is the final form of the `list.component.ts`:

```typescript
import {Component, OnInit} from '@angular/core';
import {BlogPost} from "../types";
import {HttpClient} from "@angular/common/http";
import {CommonModule} from "@angular/common";

type ApiResponse = {
  data: {
    publication: {
      posts: {
        edges: [
          {
            node: BlogPost
          }
        ]
      }
    }
  }
}


const postsQuery = `
query {
  publication(host: "compiler.blog") {
    title
    posts(first: 10) {
      edges {
        node {
          slug
          title
          brief
          url
          coverImage {
            url
            photographer
          }
        }
      }
    }
  }
}
`;

@Component({
  selector: 'app-list',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './list.component.html',
})
export class ListComponent implements OnInit {
  blogPosts: BlogPost[] = [];

  constructor(private httpClient: HttpClient) {

  }

  ngOnInit() {
    const request = this.httpClient.post<ApiResponse>(
      'https://gql.hashnode.com',
      {
        query: postsQuery
      },
      {
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json'
        }
      },
    );

    request.subscribe((response) => {
      this.blogPosts = response.data.publication.posts.edges.map(edge => edge.node);
    });
  }
}
```

### Step 2.5: Showing the blog posts

We can go ahead and edit `list/list.component.html`:

```html
<h1 class="text-3xl font-bold mb-4">Latest Blog Posts</h1>
<div class="grid grid-cols-1 gap-8">
  <div *ngFor="let post of blogPosts" class="bg-white rounded-lg shadow-md overflow-hidden">
    <img src="{{ post.coverImage.url}}" alt="Blog Post Image" class="w-full h-48 object-cover">
    <div class="p-4">
      <h2 class="text-xl font-semibold mb-2">{{ post.title }}</h2>
      <p class="text-gray-600">{{ post.brief }}</p>
      <a href="{{ post.slug }}" class="block text-blue-500 mt-2 hover:underline">Read More</a>
    </div>
  </div>
</div>
```

Ta-da! 🎉

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707641678552/4f642ccf-77b9-4fb8-9057-19631b140fcf.png align="left")

## Step 3: Creating a component for displaying single blog post

As we’re already familiar with the steps; We need to create a new component called `PostComponent`. Our basic setup is almost completely ready, so we don’t have to repeat all steps. Let’s create a component using `ng generate component post`, alternatively, you can create it yourself.

### Step 3.1: Adding a new route

Since we’re going to serve a different page from this component, we need to add a new route definition in the `src/app/app.routes.ts`:

```typescript
import { Routes } from '@angular/router';
import {PostComponent} from "./post/post.component";
import {ListComponent} from "./list/list.component";

export const routes: Routes = [
  {path: '', component: ListComponent},
  {path: ':slug', component: PostComponent}
];
```

We have a parameter called `:slug`, and will use this parameter to fetch single posts later on.

### Step 3.2: GraphQL query for a single post

We’re going to use the router parameter `:slug` to fetch the corresponding single post. Here is what the single post query looks like:

```typescript
const singlePostQuery = `
query ($slug: String!) {
  publication(host: "compiler.blog") {
    post(slug: $slug) {
      title
      brief
      publishedAt
      author {
        name
        profilePicture
      }
      content {
        html
      }
      coverImage {
        url
      }
    }
  }
}
`;
```

### Step 3.3: Extending the `BlogPost` type with content and author details

Since our single post component requires more information than the list component, we can extend the TypeScript type and define the corresponding `ApiResponse` type for the `PostComponent`:

```typescript
// imports

type BlogPostWithContentAndAuthor = BlogPost & {
  publishedAt: string
  author: {
    name: string,
    profilePicture: string
  }
  content: {
    html: string
  }
}

type ApiResponse = {
  data: {
    publication: {
      post: BlogPostWithContentAndAuthor
    }
  }
}

// @Component
```

### Step 3.3: Fetching single post from Hashnode GraphQL API

Now, we can glue everything together. One thing that will be different is that we’ll import the `DatePipe` to format date strings in our template. The final version of the component should look like this:

```typescript
import {Component, OnInit} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {BlogPost} from "../types";
import {ActivatedRoute, ActivatedRouteSnapshot} from "@angular/router";
import {DatePipe} from "@angular/common";

type BlogPostWithContentAndAuthor = BlogPost & {
  author: {
    name: string,
    profilePicture: string
  }
  content: {
    html: string
  }
}

type ApiResponse = {
  data: {
    publication: {
      post: BlogPostWithContentAndAuthor
    }
  }
}

const singlePostQuery = `
query {
  publication(host: "compiler.blog") {
    post(slug: "understanding-domain-driven-design-part-1") {
      title
      brief
      publishedAt
      author {
        name
        profilePicture
      }
      content {
        html
      }
      coverImage {
        url
      }
    }
  }
}
`;

@Component({
  selector: 'app-post',
  standalone: true,
  imports: [DatePipe],
  templateUrl: './post.component.html',
})
export class PostComponent implements OnInit {
  post: BlogPostWithContentAndAuthor = {} as BlogPostWithContentAndAuthor

  constructor(
    private httpClient: HttpClient,
    private route: ActivatedRoute
  ) {
  }

  ngOnInit() {
    const snapshot: ActivatedRouteSnapshot = this.route.snapshot;
    const slug = snapshot.params['slug'];

    const request = this.httpClient.post<ApiResponse>(
      'https://gql.hashnode.com',
      {
        query: singlePostQuery,
        variables: {
          slug: slug
        }
      },
      {
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json'
        }
      },
    );

    request.subscribe((response) => {
      this.post = response.data.publication.post;
    });
  }
}
```

### Step 3.4: Displaying single post

Let’s create a basic single blog post template. We will leverage Tailwind typography styles and wrap our content into `prose` class to show it in a beautiful way.

```html
<article class="bg-white rounded-lg shadow-md overflow-hidden">
  <img src="{{ post.coverImage.url }}" alt="{{ post.title }}" class="w-full h-64 object-cover">
  <div class="p-6">
    <h1 class="text-3xl font-bold mb-4">{{ post.title }}</h1>
    <div class="flex items-center mb-4">
      <img src="{{ post.author.profilePicture }}" alt="{{ post.author.name }}" class="w-10 h-10 rounded-full mr-2">
      <div>
        <p class="text-gray-600">Written by <span class="font-semibold">{{ post.author.name }}</span></p>
        <p class="text-gray-600">Published on <span class="font-semibold">{{ post.publishedAt | date }}</span></p>
      </div>
    </div>
    <div class="prose max-w-none" [innerHTML]="post.content.html">
    </div>
  </div>
</article>
```

There we have it! 🎉

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707680702584/db36c8fd-06f5-411b-b81a-4428286b961d.png align="center")

## Conclusion

We’ve learned how to use Hashnode GraphQL API to create custom Angular frontends. There are many examples in the [API Docs page](https://apidocs.hashnode.com/) that you can use to extend your blog’s functionality. Feel free to share your thoughts in the comments!
