Github Graphql API, Apollo Angular, Nest and Deployment with DO
Tags: dohackathon, gitalytics
Published: 2020-12-23 09:04:13

---
title: Github Graphql API, Apollo Angular, Nest and Deployment with DO
published: true
description: My draft to the DigitalOcean App Platform Hackathon! - Part 3
tags: dohackathon, gitalytics
series: Gitalytics Digital Ocean Hackathon
---

*So after setting up nx workspace, I continued to explore Github GraphQL APIs and used Apollo Angular to consume the same. Let's see how I did that!*

---

## TL;DR

This is going to be a long one, so if you're interested in code only...

{% github shhdharmen/gitalytics no-readme %}

## Table of Contents

- [1. Github GraphQL](#1-github-graphql)
- [2. API with NestJS](#2-api-with-nestjs)
  - [2.1 Configuration](#21-configuration)
      - [2.1.1 Environment Variables](#211-environment-variables)
      - [2.1.2 Variable Validation](#212-variable-validation)
      - [2.1.3 ConfigModule](#213-configmodule)
      - [2.1.4 Ignore *.env* file](#214-ignore-env-file)
  - [2.2 CORS](#22-cors)
      - [2.2.1 CORS in NestJS](#221-cors-in-nestjs)
  - [2.3 API Redirection](#23-api-redirection)
      - [2.3.1 AppController](#231-appcontroller)
      - [2.3.2 The API logic](#232-the-api-logic)
- [3. Apollo Angular](#3-apollo-angular)
    - [3.1 Installation](#31-installation)
    - [3.2 Changes *tsconfig.json*](#32-changes-tsconfigjson)
    - [3.3 API URL](#33-api-url)
    - [3.4 GraphQL Module](#34-graphql-module)
    - [3.5 Services](#35-services)
      - [3.5.1 Code Generation](#351-code-generation)
          - [3.5.1.1 GraphQL Code Generator](#3511-graphql-code-generator)
  - [3.6 Usage](#36-usage)
- [4 Deployment](#4-deployment)
  - [4.1 Scripts](#41-scripts)
  - [4.2 App Setup](#42-app-setup)
      - [4.2.1 Environment Variables](#421-environment-variables)
          - [4.2.1.1 API](#4211-api)
      - [4.2.2 Commands](#422-commands)
          - [4.2.2.1 API](#4221-api)
          - [4.2.2.2 Client](#4222-client)
- [5 Next Steps](#5-next-steps)

## 1. Github GraphQL

The [GitHub GraphQL API](https://docs.github.com/en/free-pro-team@latest/graphql) offers flexibility and the ability to define precisely the data you want to fetch.

It has only single endpoint, i.e. `https://api.github.com/graphql`. The endpoint remains constant no matter what operation you perform.

You can run queries on real GitHub data using the [GraphQL Explorer](https://docs.github.com/en/free-pro-team@latest/graphql/overview/explorer), an integrated development environment in your browser that includes docs, syntax highlighting, and validation errors.

We are also going to need a [Personal Access Token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token) to access those APIs.

Mainly, I am going to use only [`user`](https://docs.github.com/en/free-pro-team@latest/graphql/reference/queries#user) query for all operations.

## 2. API with NestJS

We created our NX workspace with angular-nest preset, so thanks to them, we already have a [nestjs](https://nestjs.com/) project configured in *apps/api*.

We are going to create only single api `/github`, which will work as redirection to `https://api.github.com/graphql`.

We could also use `https://api.github.com/graphql` directly in client project. But, to consume Github APIs, we need to access it with a PAT (i.e. Personal Access Token). Now, if we directly use at client side, it may expose your PAT.

### 2.1 Configuration

In Node.js applications, it's common to use .env files, holding key-value pairs where each key represents a particular value, to represent each environment. Running an app in different environments is then just a matter of swapping in the correct .env file.

We are going to follow [nestjs configuration](https://docs.nestjs.com/techniques/configuration) guidelines.

#### 2.1.1 Environment Variables

Create a *.env* file ar root:

```bash
PAT=PERSON_ACCESS_TOKEN
API_URL=https://api.github.com/graphql
PORT=3000
WHITELIST_URL=http://localhost:4200
NODE_ENV=development

```

| Variable      | Description                                  |
| ------------- | -------------------------------------------- |
| PAT           | GitHub Person Access Token                   |
| API_URL       | GitHub GraphQL API Endpoint                  |
| PORT          | Where you want to run your nestjs app        |
| WHITELIST_URL | Comma separated URLs to allow access to APIs |
| NODE_ENV      | Environment where app is running             |

#### 2.1.2 Variable Validation

We are also going to validate those variables, to avoid any run-time issues.

Create a file at: *apps/api/src/app/config/validation.ts*:

```typescript
import * as Joi from '@hapi/joi';

export const validationSchema = Joi.object({
  PORT: Joi.number().required(),
  PAT: Joi.string().required(),
  API_URL: Joi.string().required(),
  WHITELIST_URL: Joi.string().required(),
  NODE_ENV: Joi.string().valid('development', 'production').required(),
});

```

#### 2.1.3 ConfigModule

Let's import `ConfigModule` in `AppModule` and use `validationSchema` which we created:

```typescript
// ...
import { ConfigModule } from '@nestjs/config';
import { validationSchema } from './config/validation';

@Module({
  imports: [ConfigModule.forRoot({ validationSchema })],
  // ...
})
export class AppModule {}

```

#### 2.1.4 Ignore *.env* file

Make sure to add *.env* file in *.gitignore*:

```gitignore
.env
```

### 2.2 CORS

We are going to allow API calls only from our client (`WHITELIST_URL` from above environment). [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) can help us achieve that.

#### 2.2.1 CORS in NestJS

Under the hood, Nest makes use of the Express [cors](https://github.com/expressjs/cors) package. This package provides various options that you can customize based on your requirements.

Let's modify *main.ts* file:

```typescript
// apps/api/src/main.ts

// ...
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const allowList = configService.get<string>('WHITELIST_URL').split(',');
  const env = configService.get<string>('NODE_ENV');

  app.enableCors({
    origin: (origin, callback) => {
      // allow requests with no origin
      // (like mobile apps or curl requests)
      if (!origin && env !== 'production') return callback(null, true);

      if (allowList.indexOf(origin) === -1) {
        const msg =
          'The CORS policy for this site does not allow access from the specified Origin.';
        return callback(new Error(msg), false);
      }
      return callback(null, true);
    },
  });

  const globalPrefix = 'api';
  // rest remains same
}

bootstrap();

```

### 2.3 API Redirection

Let's create an API at `/github` route.

#### 2.3.1 AppController

```typescript
// apps/api/src/app/app.controller.ts

// ...
@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService
  ) {}

  // ...

  @Post('github')
  async github() {
    // TODO
  }
}

```

#### 2.3.2 The API logic

Below is what we want to do:

1. Read body from request
2. Read `PAT` and `API_URL` from environment
3. Forward body to GitHub API with PAT attached in Authorization
4. Return response from GitHub API

Let's first read environments:

```typescript
// apps/api/src/app/app.controller.ts

// ...
import { ConfigService } from '@nestjs/config';

export class AppController {
  private token: string;
  private api_url: string;
  constructor(
    // ...
    private readonly configService: ConfigService
  ) {
    this.token = this.configService.get('PAT');
    this.api_url = this.configService.get<string>('API_URL');
  }
  //...
}

```

To make request to GitHub API endpoint, we are going to need `HttpService`.

[Axios](https://github.com/axios/axios) is richly featured HTTP client package that is widely used. Nest wraps Axios and exposes it via the built-in `HttpModule`. The `HttpModule` exports the `HttpService` class, which exposes Axios-based methods to perform HTTP requests.

To use the `HttpService`, first import `HttpModule`.

```typescript
@Module({
  imports: [HttpModule],
})
export class AppModule {}
```

Next, inject HttpService using normal constructor injection.

```typescript
export class AppController {
  // ...
  constructor(
    // ...
    private httpService: HttpService
  ) {
    // ...
  }
  //...
}
```

And now, the API function:

```typescript
@Post('github')
async github(@Req() req: Request, @Res() res: Response) {
  const result = await this.httpService
    .post(this.api_url, req.body, {
      headers: { Authorization: 'Bearer ' + this.token },
    })
    .toPromise();
  res.status(result.status).send(result.data);
}
```

That's it! Our NestJS API is ready to serve GitHub GraphQL.

## 3. Apollo Angular

I have used [Apollo Angular](https://apollo-angular.com/) as GraphQL client. Below are the steps:

### 3.1 Installation

```bash
npm install apollo-angular @apollo/client graphql
```

### 3.2 Changes *tsconfig.json*

The `@apollo/client` package requires AsyncIterable so make sure your *tsconfig.base.json* includes `esnext.asynciterable`:

```json
{
  "compilerOptions": {
    // ...
    "lib": [
      "es2017",
      "dom",
      "esnext.asynciterable"
    ]
  }
}
```

### 3.3 API URL

Let's store the API URL in environments:

```typescript
// apps/client/src/environments/environment.ts

export const environment = {
  production: false,
  api_url: '/api',
};

```

NX created a proxy configuration that allows the Angular application to talk to the API in development.

To see how it works, open *angular.json* and find the `serve` target:

```json
{
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      "browserTarget": "client:build",
      "proxyConfig": "apps/client/proxy.conf.json"
    },
    "configurations": {
      "production": {
        "browserTarget": "client:build:production"
      }
    }
  },
}
```

Note the proxyConfig property.

Now open proxy.conf.json:

```json
{
  "/api": {
    "target": "http://localhost:3333",
    "secure": false
  }
}
```

This configuration tells nx serve to forward all requests starting with /api to the process listening on port 3333.

Change it to `http://localhost:3000`, as we're running our API at 3000 port.

Next is prod API URL:

```typescript
// apps/client/src/environments/environment.prod.ts

export const environment = {
  production: true,
  api_url: 'https://url-to-api/api',
};

```

Make sure to replace `url-to-api` with actual URL where your API project is deployed.

### 3.4 GraphQL Module

Create a GraphQL module:

```bash
ng g m graphql --project=client
```

```typescript
// apps/client/src/app/graphql/graphql.module.ts

import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { Apollo, APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache, ApolloLink } from '@apollo/client/core';
import { setContext } from '@apollo/client/link/context';
import { environment } from '../../environments/environment';

const uri = environment.api_url + '/github';

export function createApollo(httpLink: HttpLink) {
  const basic = setContext((operation, context) => ({
    headers: {
      Accept: 'charset=utf-8',
    },
  }));

  const link = ApolloLink.from([basic, httpLink.create({ uri })]);
  const cache = new InMemoryCache();

  return {
    link,
    cache,
  };
}

@NgModule({
  exports: [HttpClientModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink],
    },
  ],
})
export class GraphQLModule {}

```

And import it in `app.module.ts`:

```typescript
// apps/client/src/app/app.module.ts

// ...
@NgModule({
  // ...
  imports: [
    // ...
    GraphQLModule,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### 3.5 Services

In Apollo Angular, there are 3 APIs: `Query`, `Mutation` and `Subscription`. Each of them allows to define the shape of a result & variables. The only thing you need to do is to set the document property. That’s it, you use it as a regular Angular service.

In this approach GraphQL Documents are first-class citizens, you think about the query, for example, as a main subject. You can read more about it at [Query, Mutation, Subscription services](https://apollo-angular.com/docs/data/services).

#### 3.5.1 Code Generation

There's a tool to generate a ready to use in your component, strongly typed Angular services, for every defined query, mutation or subscription.

##### 3.5.1.1 GraphQL Code Generator

[GraphQL Code Generator](https://graphql-code-generator.com/docs/getting-started/index) is a CLI tool that can generate TypeScript typings out of a GraphQL schema.

First, we will install GraphQL Code Generator:

```bash
npm install --save-dev @graphql-codegen/cli
```

Next, let's create a basic query at *apps/client/src/.graphql*:

```graphql
query Viewer {
  viewer {
    login
  }
}
```

GraphQL Code Generator lets you set up everything by simply running the following command:

```bash
npx graphql-codegen init
```

Answer the questions:

```bash
$ npx graphql-codegen init

    Welcome to GraphQL Code Generator!
    Answer few questions and we will setup everything for you.

? What type of application are you building?           Application built with Angular

? Where is your schema?: (path or url)                 https://raw.githubusercontent.com/octokit/graphql-schema/master/schema.graphql

? Where are your operations and fragments?:            apps/client/src/.graphql

? Pick plugins:                                        TypeScript (required by other typescript plugins),
                                                       TypeScript Operations (operations and fragments),
                                                       TypeScript Apollo Angular (typed GQL services)

? Where to write the output:                           apps/client/src/generated/graphql.ts

? Do you want to generate an introspection file?       Yes

? How to name the config file?                         codegen.yml

? What script in package.json should run the codegen?  generate-codegen
```

After above, below changes are done:

1. *codegen.yml* is created at root of you project
2. `generate-codegen` is added in `scripts` section of *package.json*
3. *apps/client/src/generated/graphql.ts* is created with strongly typed Angular services, for every defined query, mutation or subscription

### 3.6 Usage

Now, it's time to consume Github APIs.

```typescript
// apps/client/src/app/home/home.component.ts

// ...
import { ViewerGQL, ViewerQuery } from '../../generated/graphql';

@Component(...)
export class HomeComponent implements OnInit {
  // ..
  viewer$: Observable<ViewerQuery['viewer']>;

  constructor(
    // ...
    private viewerGQL: ViewerGQL
  ) {}

  ngOnInit() {
    this.viewer$ = this.viewerGQL.watch().valueChanges.pipe(map((result) => result.data.viewer));

    // subscribe to get the data
    this.viewer$.subscribe((data)=>console.log(data));
  }
}

```

---

Cool, we're done with GraphQL related setup. You can run both the projects and check th results.

## 4 Deployment

We are going to create one more app on Digital Ocean for the API and connect the same repo. And we will also need some helper scripts to build the projects.

### 4.1 Scripts

First, install [npm-run-all](https://www.npmjs.com/package/npm-run-all):

```bash
npm i -D npm-run-all
```

And then add below scripts in *package.json*:

```json
{
  // ...
  "scripts": {
    // ...
    "build:prod:client": "ng build client --prod",
    "build:prod:server": "nx build api --prod",
    "build:prod": "run-s build:prod:server build:prod:client",
    "deploy:start:client": "serve -s dist/apps/client -l tcp://0.0.0.0:$PORT -n",
    "deploy:start:server": "node dist/apps/api/main.js",
    "deploy:start": "run-p deploy:start:server deploy:start:client",
  },
}
```

### 4.2 App Setup

I am going to list out app setup for both, API and Client.

#### 4.2.1 Environment Variables

##### 4.2.1.1 API

| Variable      | Value                                                   |
| ------------- | ------------------------------------------------------- |
| PAT           | GitHub Person Access Token. Also mark it as *Encrypted* |
| API_URL       | `https://api.github.com/graphql`                        |
| WHITELIST_URL | `https://url-of-your-client`                            |
| NODE_ENV      | `production`                                            |

#### 4.2.2 Commands

##### 4.2.2.1 API

| -     | Command                       |
| ----- | ----------------------------- |
| Build | `npm run build:prod:server`   |
| Run   | `npm run deploy:start:server` |

##### 4.2.2.2 Client

| -     | Command                       |
| ----- | ----------------------------- |
| Build | `npm run build:prod:client`   |
| Run   | `npm run deploy:start:client` |

## 5 Next Steps

Now I am going show data on client and charts with [ng2-charts](https://www.npmjs.com/package/ng2-charts).

If you want to have look, checkout: <https://gitalytics.shhdharmen.me/>

---

Thanks for reading.

Happy Coding

🌲 🌞 😊