DEV Community

Cover image for How to Configure GraphQL in Angular: For Public and Private APIs
Kingsley Amankwah
Kingsley Amankwah

Posted on • Edited on

3 2 2 1 1

How to Configure GraphQL in Angular: For Public and Private APIs

Introduction

Hey, Angular devs🅰️, as we already know, GraphQL provides precise data fetching and reduces over-fetching. In this tutorial, I’ll walk you through how to configure GraphQL in Angular, covering both authenticated (private APIs) and unauthenticated (public APIs) setups.

Prerequisites

Before we dive in, make sure you’ve got:

  • An Angular project, using the command ng new my-app
  • Apollo Angular Setup for GraphQL using the command ng add apollo-angular
  • Optional: State management using NgRx Signal Store for the private apis approach.

Installation

Let's set up a new angular project using:

ng new ang-grapql-setup

Enter fullscreen mode Exit fullscreen mode

After successful installations, let's proceed to install Apollo Angular using the command:

ng add apollo-angular
Enter fullscreen mode Exit fullscreen mode

This will add the following packages to the app:

  • apollo-angular - Angular integration
  • apollo/client - Core GraphQL client
  • graphql - Query language parser

Now, let's install NgRx Signals using the command:

ng add @ngrx/signals
Enter fullscreen mode Exit fullscreen mode

This will add the package @ngrx/signals to help us manage the state.

Section 1: GraphQL Setup For Public APIs

Let's start with a simple public API connection - the fastest way to get GraphQL working in the Angular app.

Configuration

Create graphql-provider-public-api.config.ts:

// src/app/config/graphql-provider-public-api.config.ts
export const apolloOptionsFactory = () => {
  const graphqlURI = environment.GRAPHQL_URI_NO_TOKEN;
  const httpLink = inject(HttpLink);

  return {
    link: httpLink.create({ uri: graphqlURI }),
    cache: new InMemoryCache(),
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'cache-first',
        errorPolicy: 'all',
      },
      query: {
        fetchPolicy: 'cache-first',
        errorPolicy: 'all',
      },
    },
  };
};

export const graphqlProviderConfig: ApplicationConfig['providers'] =
  [
    Apollo,
    {
      provide: APOLLO_OPTIONS,
      useFactory: apolloOptionsFactory,
    },
  ];

Enter fullscreen mode Exit fullscreen mode

In the above code, the apolloOptionsFactory function pulls in the GraphQL server URI from your environment config and uses Angular’s inject to grab the HttpLink service, creating a direct connection to the endpoint.

We set up an InMemoryCache to store query results, reducing unnecessary network calls and apply default options like cache-first to prioritize cached data while fetching fresh results when needed.

The errorPolicy: "all" ensures we get both data and errors, giving us the flexibility in handling responses.

Finally, the graphqlProviderConfig ties it all together, providing Apollo Client to the app.

App Configuration

Update app.config.ts with the graphqlProviderConfig

// src/app/app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient(),
    graphqlProviderConfig,
  ],
};

Enter fullscreen mode Exit fullscreen mode

Setup Public API Query

Let’s set up a query from a public GraphQL API to fetch country details.

// src/app/graphql/queries.ts
export const FETCH_COUNTRIES = gql`
  query {
    countries {
      name
      capital
      currency
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Now, let's set up a countries component to fetch and display the data:

// src/app/countries/countries.component.ts
@Component({
  selector: 'app-countries',
  imports: [CommonModule],
  templateUrl: './countries.component.html',
})
export class CountriesComponent {
  protected data: Countries[] = [];

  constructor(private readonly apollo: Apollo) {
    this.apollo
      .query<CountriesResponse>({ query: FETCH_COUNTRIES })
      .subscribe((result) => (this.data = result.data.countries));
  }
}
Enter fullscreen mode Exit fullscreen mode

Displaying the Data

Now the countries template, we set up to display the countries' data we need:

// src/app/countries/countries.component.html
@if (data; as countries) {
<div class="grid">
  @for (country of countries; track country.name) {
  <div class="card">
    <h3>{{ country.name }}</h3>
    <p>Currency: {{ country.currency }}</p>
    <p>Capital: {{ country.capital || "N/A" }}</p>
  </div>
  }
</div>
}

Enter fullscreen mode Exit fullscreen mode

Expected Output:

Here's how the output looks when everything is working correctly from the countries' public API based on the query setup:

Countries showing their capital and currencies

The view shows countries their capital and currency

Section 2: GraphQL Setup For Private API's With Authentication

For us to access a private API, we will need an access token, and so for that, let's update or create a new config file graphql-provider-private-api.config.ts with these improvements:

// src/app/config/graphql-provider-private-api.config.ts
export const apolloOptionsFactory = () => {
  const graphqlURI = environment.GRAPHQL_URI_WITH_TOKEN;
  const httpLink = inject(HttpLink);
  const userStore = inject(UserStore);

  const errorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      graphQLErrors.forEach(({ message }) => {
        console.error(`[GraphQL error]: ${message}`);
        if (message.includes('Bad credentials')) {
          userStore.logout();
        }
      });
    }
    if (networkError) {
      console.error(`[Network error]: ${networkError.message}`);
    }
  });

  const authMiddleware = new ApolloLink((operation, forward) => {
    const token = userStore.authData().app_token;

    if (!token) {
      console.warn('No token available');
      return forward(operation);
    }

    operation.setContext({
      headers: new HttpHeaders().set('Authorization', `Bearer ${token}`),
    });

    return forward(operation);
  });

  const httpLinkHandler = httpLink.create({ uri: graphqlURI });
  const link = ApolloLink.from([errorLink, authMiddleware, httpLinkHandler]);

  return {
    link,
    cache: new InMemoryCache(),
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'network-only',
        errorPolicy: 'all',
      },
      query: {
        fetchPolicy: 'network-only',
        errorPolicy: 'all',
      },
    },
  };
};

export const graphqlProviderConfig: ApplicationConfig['providers'] = [
  Apollo,
  {
    provide: APOLLO_OPTIONS,
    useFactory: apolloOptionsFactory,
  },
];

Enter fullscreen mode Exit fullscreen mode

In this configuration, the apolloOptionsFactory constructs a secure GraphQL client through three layered operations.

First, the errorLink handles authentication failures by automatically logging out users when it detects invalid credentials and logs all GraphQL/network errors.

Second, the authMiddleware intercepts each request to inject the bearer token from UserStore into authorization headers, gracefully bypassing when no token exists.

Finally, the httpLinkHandler establishes the connection to the GraphQL endpoint. These links chain together through ApolloLink.from(), creating a pipeline that wraps every request with authentication and error handling while maintaining consistent cache-first query policies across the application.

Update App Configuration

The application configuration in app.config.ts follows the same pattern as the public api setup but now imports the authentication-ready provider from the new configuration file src/app/config/graphql-provider-private-api.config.ts:

// src/app/app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient(),
    graphqlProviderConfig,
  ],
};
Enter fullscreen mode Exit fullscreen mode

State Management for Private APIs

While the focus is on GraphQL configuration, we need a robust way to manage the authentication state for private APIs, so for that we're going to leverage the Signal Store for this implementation.

Here's how we'll handle the state management:

// src/app/store/user.store.ts
const initialState = {
  authData: { app_token: '' },
  isAuthenticated: false
};

export const UserStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withMethods((store) => ({
    setToken(token: string) {
      patchState(store, {
        authData: { app_token: token },
        isAuthenticated: !!token
      });
      localStorage.setItem('github_token', token);
    },
    initialize() {
      const token = localStorage.getItem('github_token');
      if (token) {
        this.setToken(token);
      }
    },
    logout() {
      patchState(store, initialState);
      localStorage.removeItem('github_token');
    }
  }))
);
Enter fullscreen mode Exit fullscreen mode

Authentication Flow: Login Component

Before we can make authenticated queries, we need a way to collect and store the private api token.

Here's a minimal login component that bridges our state management and GraphQL setup:

// src/app/login/login.component.ts
@Component({
  selector: 'app-login',
  imports: [FormsModule],
  templateUrl: './login.component.html',
})
export class LoginComponent {
  protected token = '';
  private readonly userStore = inject(UserStore);
  private readonly router = inject(Router);

  login() {
    if (this.token) {
      this.userStore.setToken(this.token);
      this.router.navigate(['/profile']);
    }
  }
}

// src/app/login/login.component.html
<div>
  <h2>Login with GitHub Token</h2>
  <input [(ngModel)]="token" type="password" placeholder="GitHub Token" />
  <button (click)="login()">Login</button>
</div>

Enter fullscreen mode Exit fullscreen mode

Making Authenticated GraphQL Queries

Now that we have our authentication flow done, let's set up a query from a private GitHub's GraphQL API to get a user's GitHub profile details.

// src/app/graphql/queries.ts
export const FETCH_USER = gql`
  query {
    viewer {
      login
      name
      email
      bio
      avatarUrl
      company
      location
      websiteUrl
      repositories(first: 5, orderBy: { field: UPDATED_AT, direction: DESC }) {
        nodes {
          name
          description
          url
          createdAt
          updatedAt
          isPrivate
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

Implementing the Authenticated Component

With our GraphQL query ready, let's examine the component that brings everything together. This AuthenticatedComponent demonstrates:

  1. Token verification before making requests
  2. Proper loading and error states
  3. Type-safe data handling
  4. Automatic logout on authentication failures
// src/app/authenticated/authenticated.component.ts
@Component({
  selector: 'app-authenticated',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './authenticated.component.html'
})
export class AuthenticatedComponent {
  private readonly apollo = inject(Apollo);
  private readonly userStore = inject(UserStore);
  private readonly router = inject(Router);

  userData: GitHubUser | null = null;
  error: string | null = null;
  loading = false;

  ngOnInit() {
    this.userStore.initialize();
    this.fetchUserData();
  }

  protected fetchUserData() {
    if (!this.userStore.isAuthenticated()) {
      this.router.navigate(['/login']); 
      return;
    }

    this.loading = true;
    this.error = null;

    this.apollo.query<GitHubUserResponse>({
      query: FETCH_USER,
    }).subscribe({
      next: ({ data }) => {
        this.userData = data.viewer;
        this.loading = false;
      },
      error: (err) => {
        this.error = err.message;
        this.loading = false;
        this.userStore.logout(); 
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Displaying the Data

The template uses Angular's new @if syntax for conditional rendering:

// src/app/authenticated/authenticated.component.html
@if (loading) {
<p>Loading GitHub profile...</p>

} @if (error) {
<p class="error">Sorry something went wrong</p>
} @if (userData) {

<div class="profile-header">
  <img [src]="userData.avatarUrl" width="100px" alt="Avatar" class="avatar" />
  <h2>{{ userData.name }}</h2>
  <p>{{ userData.login }}</p>
  <p>{{ userData.bio }}</p>
  <p *ngIf="userData.company">Works at {{ userData.company }}</p>
  <p *ngIf="userData.location">📍 {{ userData.location }}</p>
  <p *ngIf="userData.websiteUrl">
    <a [href]="userData.websiteUrl" target="_blank">{{
      userData.websiteUrl
    }}</a>
  </p>
</div>

<h3>Recent Repositories</h3>
<div class="repositories">
  <div class="repo-card">
    @for (repo of userData.repositories.nodes; track repo.name) {
    <h4>
      <a [href]="repo.url" target="_blank">{{ repo.name }}</a>
      @if (repo.isPrivate) {
      <span class="private-badge">Private</span>
      }
    </h4>

    @if (repo.description) {
    <p>{{ repo.description }}</p>
    }
    <div class="repo-meta">
      <span>Updated {{ repo.updatedAt | date }}</span>
    </div>
    }
  </div>
</div>
}

Enter fullscreen mode Exit fullscreen mode

After implementing these components, our Angular application now successfully:

  • Authenticates with GitHub's GraphQL API
  • Fetches protected user data
  • Displays user profile with repository information

Here's how the final output looks when everything is working correctly:

Authenticated GitHub Profile Display

The authenticated view showing user profile and recent repositories

Key Takeaways:

Feature No Auth Setup With Auth Setup
Configuration Simple HttpLink Auth middleware + error handling
State Management None signal store with token persistence
Error Handling Basic console logging Automatic token cleanup + user feedback
Data Freshness cache-first network-only for sensitive data
Security Public API only Token in headers + localStorage
Persistence No token storage Automatic token rehydration on app start
UI Integration Simple data display Loading states + error messages

Conclusion

In this tutorial, we learned how to setup and make use of GraphQL public apis and private apis(requires authentication) in an angular application.

By understanding this setup, the same patterns can be adapted to work with any GraphQL API in Angular.

Explore the complete project:

🔗 GitHub Repository

Connect with me:

💼 LinkedIn

🐦 X (Twitter)

Happy coding! 🚀

Top comments (8)

Collapse
 
nevodavid profile image
Nevo David

Great guide on using GraphQL in Angular; very useful tips shared!

Collapse
 
sh20raj profile image
Sh Raj

Hey! 👋 I’ve been exploring different ways to turn ideas into functional UI faster, and recently came across (and started using) SketchFlow — a tool that helps convert rough sketches or wireframes into clean HTML/Tailwind code. It's super handy when you want to focus on flow and layout before diving deep into styling or logic.

Still early days, but it’s been a neat productivity boost for me. Would love to know if anyone else has tried something similar — always curious about tools that speed up the dev-design loop! 💡

Collapse
 
kingsley profile image
Kingsley Amankwah

I'm curious about such tools; let me have a look at it ASAP.
Thanks for sharing @sh20raj

Collapse
 
kingsley profile image
Kingsley Amankwah

Yeah, stay tuned for more content, Nevo

Collapse
 
raju_dandigam profile image
Raju Dandigam

Good one

Collapse
 
kingsley profile image
Kingsley Amankwah

You're welcome @raju_dandigam

Collapse
 
akuoko_konadu profile image
Konadu Akwasi Akuoko

This is so good, learnt a lot. Thanks Sir

Collapse
 
kingsley profile image
Kingsley Amankwah

You're welcome Akwasi