A Quick Guide to Angular and GraphQL

Over the past five years, GraphQL has established itself as the most popular alternative to REST APIs. GraphQL has several advantages over traditional REST-based services. First of all, GraphQL makes the query schema available to the clients. A client that reads the schema immediately knows what services are available on the server. On top of that, the client is able to perform a query on a subset of the data.

Both of these features make the API much more flexible. The server can freely extend the API or make different parts of it available to different clients without breaking any client code. Another major advantage is the ability to perform complex queries. This reduces the number of API calls a client has to perform, and therefore improves performance.

In this tutorial, I will show you how to consume GraphQL in an Angular client. First, I will create a simple GraphQL server using Express. Then I will use the Apollo library to create a service in Angular that connects to the API and performs queries on it. I will implement a simple application that lets you browse the characters of the Star Wars franchise and look up details about their races and home planets. I will not assume any prior knowledge of Express, Angular, or GraphQL. I will assume that you are familiar with JavaScript and Node and have an up-to-date version of the npm package manager installed on your computer.

Prerequisites:

Table of Contents

Implement a GraphQL server with Express

In this section, I will show you how to implement the GraphQL server using the Express framework. To start, open your terminal in an empty folder of your choice. This will be the folder that will contain the server project. Inside that folder, run the following command to initialize your Express project.

npm init -y

This will create a package.json in your folder containing the project information and dependencies. Next, you need to install the dependencies required for this project. In your terminal, run the following command.

npm i -E express@4.17.1 graphql@15.6.1 express-graphql@0.12.0 cors@2.8.5 \
  body-parser@1.19.0 csv-parse@4.16.3

To make the server work, you will need some data. I have chosen to keep it simple and use existing CSV data from Kaggle, a machine learning and data science community. Joe Young has put together Star Wars character data from the Fandom Wiki and made it freely available at this link https://www.kaggle.com/jsphyg/star-wars. You will need only two files from his collection for this project characters.csv and species.csv. You might have to create a Kaggle account to download the data. Place the files into your project folder.

GraphQL uses a global schema to define the queries allowed on the API and the data they return. Create a file schema.graphql and paste the following contents into it.

type Query {
  characters(offset: Int = 0, limit: Int = 10): CharactersResult
  character(name: String!): Character
  species(name: String!): Species
}

type CharactersResult {
  count: Int
  characters: [Character]
}

type Character {
  name: String
  height: Int
  mass: String
  hair_color: String
  skin_color: String
  eye_color: String
  birth_year: String
  gender: String
  homeworld: String
  species: String
}

type Species {
  name: String
  classification: String
  designation: String
  average_height: String
  skin_colors: String
  hair_colors: String
  eye_colors: String
  average_lifespan: String
  language: String
  homeworld: String
}

The Query type defines the entry point of the schema. It defines three queries: characters, character, and species. The data returned by these queries is defined in separate types in the schema file. Now, create a new file app.js, and fill it with the code below.

const express = require('express');
const cors = require('cors');
const { json } = require('body-parser');
const fs = require('fs');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const parse = require('csv-parse/lib/sync');

const app = express()
  .use(cors())
  .use(json());

const schema = buildSchema(fs.readFileSync('schema.graphql', 'utf8'));
const characters = parse(fs.readFileSync('characters.csv', 'utf8'), { columns: true });
const species = parse(fs.readFileSync('species.csv', 'utf8'), { columns: true });

const root = {
  characters: (args) => {
    return {
      count: characters.length,
      characters: characters.slice(args.offset, args.offset + args.limit)
    };
  },
  character: (args) => {
    return characters.find((ch) => ch.name === args.name);
  },
  species: (args) => {
    return species.find((ch) => ch.name === args.name);
  },
};

app.use('/graphql', graphqlHTTP({
  schema,
  rootValue: root,
  graphiql: true,
}));

app.listen(4201, (err) => {
  if (err) {
    return console.log(err);
  }
  return console.log('Server listening on port 4201');
});

This is all the code you need to write to create the GraphQL server. The graphqlHTTP middleware registers the handlers on the /graphql route of the express server. The root object defines so-called reducers that will be called whenever a client queries the API. They are responsible for collecting and returning the data.

The nice thing about GraphQL is that you can immediately try it out. In your terminal, run the following command.

node app.js

Now open your browser at http://localhost:4201/graphql and you will be presented with an interactive console.

The GraphQL interactive console

On the left side, you can try out queries. Give it a go by pasting the following query into the query editor in the browser and pressing the play button at the top.

{
  characters(offset: 2, limit:3) {
    count
    characters {
      name
      homeworld
      species
    }
  }
  species(name:"Human") {
    classification
    homeworld
  }
}

You should see this result on the right side of the screen.

{
  "data": {
    "characters": {
      "count": 87,
      "characters": [
        {
          "name": "R2-D2",
          "homeworld": "Naboo",
          "species": "Droid"
        },
        {
          "name": "Darth Vader",
          "homeworld": "Tatooine",
          "species": "Human"
        },
        {
          "name": "Leia Organa",
          "homeworld": "Alderaan",
          "species": "Human"
        }
      ]
    },
    "species": {
      "classification": "mammal",
      "homeworld": "Coruscant"
    }
  }
}

Create an Angular GraphQL client

Angular provides a command-line tool that makes it easy for anybody to set up and maintain an Angular project. The Angular CLI tool can be installed globally using npm by running the following command.

npm install -g @angular/cli@12.2.10

This package provides the global ng command. With this, open the terminal in a folder of your choice and create a new project by running the command below.

ng new graphql-client --routing --style=css

This will create a folder graphql-client, install the Angular framework into it, set up all the necessary toolchains, and create a default application skeleton that you can extend.

The Apollo library offers a convenient GraphQL client. Navigate into the new project folder and add it to the project by running the following command.

ng add apollo-angular

When asked to confirm the installation of the library, answer with y. Next, you will be asked to provide the endpoint URI. Type in http://localhost:4201/graphql and press enter. Now that the basic setup is done, you are ready to create a few components.

Start by creating three components: a Navbar for the page navigation, Home for the home page, and Browse, containing a page to let you browse through the query results. In the terminal, run the following commands.

ng generate component navbar --module=app
ng generate component home --module=app
ng generate component browse --module=app

In an editor of your choice, open src/app/app.component.html and replace its content with the following two lines.

<app-navbar></app-navbar>
<router-outlet></router-outlet>

There are only two components here. The first will show the navigation bar. The <router-outlet> is a special component in Angular. The Angular router is responsible for reading the navigation URL and deciding which component to show based on the requested path. The <router-outlet> will then show that component.

Next, open src/app/navbar/navbar.component.html and paste the code below into the file.

<div class="navbar">
  <div>
    GraphQL Angular App
  </div>
  <nav>
    <ul>
      <li><a routerLink="/">Home</a></li>
      <li><a routerLink="/browse">Browse</a></li>
    </ul>
  </nav>
</div>

Notice the routerLink properties on the anchor tags. These links are similar to relative links inside regular HTML. But instead of reloading the page, they instruct the router to replace the active component in the router-outlet.

The navigation bar could use some styling. Open src/app/navbar/navbar.component.css and fill in the CSS code below.

.navbar {
  padding: 8px 16px;
  background-color: #333333;
  color: #ffffff;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

nav ul {
  display: flex;
  list-style: none;
  padding: 0;
}

nav li {
  margin-left: 8px;
  margin-right: 8px;
}

a {
  color: #ffffff;
  text-decoration: none;
}

Now, update the Home component by pasting the following line into src/app/home/hone.component.html.

<h1>Consuming GraphQL with Angular</h1>

To style the component, open src/app/home/hone.component.css and add the following code.

h1 {
  text-align: center;
  margin: 4rem;
}

Before you can start work on the Browse component, you need a service class that encapsulates the calls to the GraphQL API. This class is the core part of this application and demonstrates just how easy it is to communicate with the server using GraphQL. Open the terminal in your project folder and run the following command.

ng generate service characters

This will create a new file src/app/characters.service.ts. Open this file in your editor and replace its contents with the code below.

import { Injectable } from '@angular/core';
import { Apollo, gql, QueryRef } from 'apollo-angular';

export interface Character {
  name: string;
  homeworld: string;
  species: string;
}

export interface CharacterDetail extends Character {
  height: number;
  mass: string;
  hair_color: string;
  skin_color: string;
  eye_color: string;
  birth_year: string;
  gender: string;
}

export interface CharactersResult {
  count: number;
  characters: Character[];
}

export interface Species {
  name: string;
  classification: string;
  designation: string;
  average_height: string;
  skin_colors: string;
  hair_colors: string;
  eye_colors: string;
  average_lifespan: string;
  language: string;
  homeworld: string;
}

@Injectable({
  providedIn: 'root'
})
export class CharactersService {

  private charactersQuery: QueryRef<{characters: CharactersResult}, { offset: number}>;
  private findCharacterQuery: QueryRef<{character: CharacterDetail}, { name: string}>;
  private findSpeciesQuery: QueryRef<{species: Species}, { name: string}>;

  constructor(private apollo: Apollo) { 
    this.charactersQuery = this.apollo.watchQuery({
      query: gql`query characters($offset: Int!) {
        characters(offset: $offset) {
          count
          characters {
            name
            homeworld
            species
          }
        }
      }`
    });

    this.findCharacterQuery = this.apollo.watchQuery({
      query: gql`query character($name: String!) {
        character(name: $name) {
          name
          height
          mass
          hair_color
          skin_color
          eye_color
          birth_year
          gender
          homeworld
          species
        }
      }`
    });

    this.findSpeciesQuery = this.apollo.watchQuery({
      query: gql`query species($name: String!) {
        species(name: $name) {
          name
          classification
          designation
          average_height
          skin_colors
          hair_colors
          eye_colors
          average_lifespan
          language
          homeworld
        }
      }`
    });
  }

  async getCharacters(offset: number): Promise<CharactersResult> {
    const result = await this.charactersQuery.refetch({ offset });
    return result.data.characters;
  }

  async findCharacter(name: string): Promise<CharacterDetail> {
    const result = await this.findCharacterQuery.refetch({ name });
    return result.data.character;
  }

  async findSpecies(name: string): Promise<Species> {
    const result = await this.findSpeciesQuery.refetch({ name });
    return result.data.species;
  }
}

This implementation of the service consists of three parts. At the top of the file, I declared the TypeScript types related to this service. Each type represents the result of a query. The second part is the constructor. Here, the Apollo queries are defined. The queries make use of the gql tag to template strings. This tag translates the strings to GraphQL documents. Finally, the member functions of the service make the queries publicly available to components in your application.

The queries are asynchronous and return promises that resolve the query result once a response from the server has been received.

With this, you can now implement the Browse component. Open src/app/browse/browse.component.ts and change its content to match the following code.

import { Component, OnInit } from '@angular/core';
import { Character, CharactersService } from '../characters.service';

@Component({
  selector: 'app-browse',
  templateUrl: './browse.component.html',
  styleUrls: ['./browse.component.css']
})
export class BrowseComponent implements OnInit {
  offset: number = 0;
  count: number = 0;
  characters: Character[] = [];

  constructor(private charactersService: CharactersService) {}

  async ngOnInit(): Promise<void> {
    await this.updateCharacters();
  }

  async updateCharacters() {
    const result = await this.charactersService.getCharacters(this.offset);
    this.count = result.count;
    this.characters = result.characters;
  }

  showPrevious() {
    return this.offset > 0;
  }

  showNext() {
    return this.offset + 10 < this.count;
  }

  async onPrevious() {
    this.offset -= 10;
    await this.updateCharacters();
  }

  async onNext() {
    this.offset += 10;
    await this.updateCharacters();
  }
}

The function updateCharacters() uses the CharactersService to obtain a list of characters from the server. The functions onPrevious() and onNext() are callbacks that can be used to page through the results. Now, open src/app/browse/browse.component.html and copy the following contents into it.

<div class="browse">
  <h1>Browse Characters</h1>
  <table>
    <tr>
      <th>Name</th>
      <th>Homeworld</th>
      <th>Species</th>
    </tr>
    <tr *ngFor="let character of characters">
      <td><a [routerLink]="['/character']" [queryParams]="{name: character.name}">{{character.name}}</a></td>
      <td>{{character.homeworld}}</td>
      <td><a [routerLink]="['/species']" [queryParams]="{name: character.species}">{{character.species}}</a></td>
    </tr>
  </table>
  <div class="pager">
    <button class="prev" *ngIf="showPrevious()" (click)="onPrevious()"> prev </button>
    <button class="next" *ngIf="showNext()" (click)="onNext()"> next </button>
  </div>
</div>

The component displays the character data in a table and provides navigation buttons to page through the results. It also provides links to routes that will display details of a character or its species. You will be implementing those routes later.

The CSS file src/app/browse/browse.component.css can be used to provide some styling to the browse component. Update the contents to the code below.

.browse {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.browse > * {
  width: 100%;
  max-width: 800px;
}

table {
  margin-top: 16px;
  margin-bottom: 16px;
  border-collapse: collapse;
  border: 1px solid #bbbbbb;
}

tr:nth-child(odd) {
  background-color: #dddddd;
}

th, td {
  padding: 4px 8px;
  border-right: 1px solid #bbbbbb;
  border-left: 1px solid #bbbbbb;
}

.prev {
  float: left;
}

.next {
  float: right;
}

Now, create the two components that will display the character and species details. Open the terminal again and type in the following commands.

ng generate component character --module=app
ng generate component species --module=app

This will create the files for the two components. Open src/app/character/character.component.ts and paste the following code into it.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CharacterDetail, CharactersService } from '../characters.service';

@Component({
  selector: 'app-character',
  templateUrl: './character.component.html',
  styleUrls: ['./character.component.css']
})
export class CharacterComponent implements OnInit {
  character!: CharacterDetail;

  constructor(private route: ActivatedRoute, private characterService: CharactersService) { }

  ngOnInit(): void {
    this.route.queryParams.subscribe(async (params) => {
      this.character = await this.characterService.findCharacter(params.name);
    });
  }
}

This is a simple component that reads the character’s name from the query parameters in the URL and then uses CharactersService to obtain the character detail. The corresponding template simply shows the queried data. Open src/app/character/character.component.html and copy the following contents into it.

<div *ngIf=character>
  <p><strong>Name:</strong> {{character.name}}</p>
  <p><strong>Mass:</strong> {{character.mass}}</p>
  <p><strong>Hair Color:</strong> {{character.hair_color}}</p>
  <p><strong>Skin Color:</strong> {{character.skin_color}}</p>
  <p><strong>Eye Color:</strong> {{character.eye_color}}</p>
  <p><strong>Birth Year:</strong> {{character.birth_year}}</p>
  <p><strong>Gender:</strong> {{character.gender}}</p>
</div>
<a [routerLink]="['/browse']">Back</a>

The component showing the species information follows the same pattern. Open src/app/species/species.component.ts and update the content to match the code below.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CharactersService, Species } from '../characters.service';

@Component({
  selector: 'app-species',
  templateUrl: './species.component.html',
  styleUrls: ['./species.component.css']
})
export class SpeciesComponent implements OnInit {
  species!: Species;

  constructor(private route: ActivatedRoute, private characterService: CharactersService) { }

  ngOnInit(): void {
    this.route.queryParams.subscribe(async (params) => {
      this.species = await this.characterService.findSpecies(params.name);
    });
  }
}

Next, open src/app/species/species.component.html and paste in the code below.

<div *ngIf=species>
  <p><strong>Name:</strong> {{species.name}}</p>
  <p><strong>Classification:</strong> {{species.classification}}</p>
  <p><strong>Designation:</strong> {{species.designation}}</p>
  <p><strong>Hair Colors:</strong> {{species.hair_colors}}</p>
  <p><strong>Skin Colors:</strong> {{species.skin_colors}}</p>
  <p><strong>Eye Colors:</strong> {{species.eye_colors}}</p>
  <p><strong>Average Lifespan:</strong> {{species.average_lifespan}}</p>
  <p><strong>Language:</strong> {{species.language}}</p>
  <p><strong>Homeworld:</strong> {{species.homeworld}}</p>
</div>
<a [routerLink]="['/browse']">Back</a>

I already mentioned how the Angular router is responsible for deciding which component to render, based on the path in the URL. In the file src/app/app-routing.module.ts you can define these associations.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { BrowseComponent } from './browse/browse.component';
import { CharacterComponent } from './character/character.component';
import { SpeciesComponent } from './species/species.component';

const routes: Routes = [
  {
    path: '',
    component: HomeComponent
  },
  {
    path: 'browse',
    component: BrowseComponent,
  },
  {
    path: 'character',
    component: CharacterComponent,
  },
  {
    path: 'species',
    component: SpeciesComponent,
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

You are now ready to test the application. Make sure your server is running as described in the previous section. Then open a terminal in the client Angular project folder and run the following command.

ng serve

Open your browser, navigate to http://localhost:4200 and you should see the homepage of your app. You can use the navigation bar to go to the browse page. You should see something like the image below.

Testing the character browser

Integrate OIDC for auth

Every good application needs some user control. Okta lets you add user authentication easily to your application.

Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Single-Page App and press Enter.

Use http://localhost:4200/login/callback for the Redirect URI and set the Logout Redirect URI to http://localhost:4200.

What does the Okta CLI do?

The Okta CLI will create an OIDC Single-Page App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. It will also add a trusted origin for http://localhost:4200. You will see output like the following when it’s finished:

Okta application configuration:
Issuer:    https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6

NOTE: You can also use the Okta Admin Console to create your app. See Create an Angular App for more information.

Make a note of the Issuer and the Client ID. You will need them in the next steps.

Add JWT authentication to the server

Adding authentication to the server is easy. Open a terminal in the server’s project folder and install a few more dependencies by running the following command.

npm i -E @okta/jwt-verifier@2.3.0 express-bearer-token@2.4.0

Now, create a new file auth.js with the content below.

const OktaJwtVerifier = require('@okta/jwt-verifier');

const oktaJwtVerifier = new OktaJwtVerifier({
  clientId: '{yourClientID}',
  issuer: 'https://{yourOktaDomain}/oauth2/default'
});

module.exports = async function oktaAuth(req, res, next) {
  try {
    const token = req.token;
    if (!token) {
      return res.status(401).send('Not Authorized');
    }
    await oktaJwtVerifier.verifyAccessToken(token, 'api://default');
    next();
  }
  catch (err) {
    return res.status(401).send(err.message);
  }
};

This module exports an Express middleware that checks the bearer token of an incoming request and verifies its validity. To use this middleware, open app.js and add the following require statements to the top of the file.

const bearerToken = require('express-bearer-token');
const oktaAuth = require('./auth');

Then, update the creation of the Express server with the declaration of the middleware to match the following.

const app = express()
  .use(cors())
  .use(json())
  .use(bearerToken())
  .use(oktaAuth);

The bearerToken middleware extracts the bearer token from a request header. oktaAuth then checks the token. You can now run node app.js again to start the server. Only now, if you try to access the API you will get a 401 Unauthorized error response from the server.

Add Okta to Angular

To allow the client to access the GraphQL API, you need to also add Okta authentication to the Angular application. Open a terminal in the client’s project folder and install the okta-angular dependency by running the following command.

npm install -E @okta/okta-angular@4.1.0 @okta/okta-auth-js@5.6.0

Open src/app/app.module.ts and create an OktaAuth instance by adding the following lines after the existing import statements.

import { OKTA_CONFIG, OktaAuthModule } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';

const config = {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  clientId: '{yourClientID}',
  redirectUri: window.location.origin + '/login/callback'
}
const oktaAuth = new OktaAuth(config);

Make sure to replace the {...} placeholders with your OIDC app settings.

Next, add OktaAuthModule to the array of imports in the NgModule configuration.

@NgModule({
  ...
  imports: [
    ...
    OktaAuthModule
  ],
  ...
})

Also, create a new providers property that configures the module by pasting the code below after the imports array.

providers: [{ provide: OKTA_CONFIG, useValue: { oktaAuth }}],

You now need to configure the router to accept the login callback from Okta. Open src/app/app-routing.module.ts and add the following import statement to the top of the file.

import { OktaAuthGuard, OktaCallbackComponent } from '@okta/okta-angular';

Update the routes configuration object to match the code below.

const routes: Routes = [
  {
    path: '',
    component: HomeComponent
  },
  {
    path: 'browse',
    component: BrowseComponent,
    canActivate: [ OktaAuthGuard ]
  },
  {
    path: 'character',
    component: CharacterComponent,
    canActivate: [ OktaAuthGuard ]
  },
  {
    path: 'species',
    component: SpeciesComponent,
    canActivate: [ OktaAuthGuard ]
  },
  {
    path: 'login/callback',
    component: OktaCallbackComponent
  },
];

Here you have added the OktaAuthGuard to the three routes that display character data. When a user who isn’t logged in attempts to access these routes, they will now automatically be redirected to the Okta sign-in page before gaining access.

In the last section, you added authentication to the server. This means that any access to the server now needs to provide an access token. To provide this token, open src/app/graphql.module.ts and replace the existing createApollo function with the implementation below.

import { setContext } from '@apollo/client/link/context';
import { OktaAuth } from '@okta/okta-auth-js';

const uri = 'http://localhost:4201/graphql'; 
export function createApollo(httpLink: HttpLink, oktaAuth: OktaAuth): ApolloClientOptions<any> {
  const http = httpLink.create({ uri });
  const auth = setContext(async (_, { headers }) => {
    const token = oktaAuth.getAccessToken();

    return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
  });

  return {
    link: auth.concat(http),
    cache: new InMemoryCache()
  };
}

The function now expects an OktaAuth instance as the second argument. This means that you have to add it to the deps array in the NgModule provider for the Apollo options. Update the deps array to match the following.

deps: [HttpLink, OktaAuth]

Finally, you want to provide feedback to the user’s sign-in status and also provide manual ways of logging in and out of the application. Open src/app/navbar/navbar.component.ts and replace its contents with the code below.

import { Component } from '@angular/core';
import { OktaAuthStateService } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent {
  
  constructor(public authStateService: OktaAuthStateService, private oktaAuth: OktaAuth) {}

  async login() {
    await this.oktaAuth.signInWithRedirect();
  }

  async logout() {
    await this.oktaAuth.signOut();
  }

}

The OktaAuthStateService can be queried to provide the user’s status. The callbacks login() and logout() can be used to sign the user in and out. Now, open src/app/navbar/navbar.component.html and add the following lines as the first element inside the <nav> element.

<div>
  <button *ngIf="!(authStateService.authState$ | async)?.isAuthenticated" (click)="login()">Login</button>
  <button *ngIf="(authStateService.authState$ | async)?.isAuthenticated" (click)="logout()">Logout</button>
</div>

This will show a Login button if the user is not logged in and a Logout button if the user is logged in.

Congratulations! You have completed the implementation of an Angular app that consumes GraphQL from a Node/Express server. The application is secured with Okta’s authentication service. You can now start the app by running ng serve in your project folder. Make sure the server is also running as described above. Open your browser to http://localhost:4200 to open the app. When you click on Browse in the navigation bar, you will be taken to the Okta sign-in page. Once you logged in, you will be redirected to the character browser. You should see something like the image below.

Running the complete application with authentication

Learn more about Angular, GraphQL, and single-page applications

In this tutorial, I have shown you how to create a single-page application that consumes a GraphQL API using Angular. You’ve seen how to create a GraphQL schema that describes the queries and the results that the server supplies.

GraphQL provides a flexible way of implementing APIs that can be extended without breaking existing clients. Clients can specify the data they want to receive and can combine multiple requests into a single query. This makes GraphQL faster than traditional REST APIs because the number of API calls can be greatly reduced.

The app you have written consists of a server and a client. The server uses Express to parse incoming requests before passing them to the GraphQL middleware. The client is built using Angular and uses the Apollo library to interface with the GraphQL API. If you want to learn more about GraphQL, Angular, or single-page apps in general, feel free to follow the links below.

You can find the code for this tutorial on GitHub at https://github.com/oktadev/okta-angular-graphql-example.

If you liked this tutorial, chances are you like others we publish. Please follow @oktadev on Twitter and subscribe to our YouTube channel to get notified when we publish new developer tutorials.

Okta Developer Blog Comment Policy

We welcome relevant and respectful comments. Off-topic comments may be removed.