Getting started with a Angular/NX workspace backed by an AWS Amplify GraphQL API - Part 1
Tags: angular, graphql, awsamplify, awsappsync
Published: 2019-03-26 17:01:56

---
title: Getting started with a Angular/NX workspace backed by an AWS Amplify GraphQL API - Part 1
published: true
description: We're building a simple Todo-App, but with a setup in mind that is targeted towards a non-trivial multi-app, multi-team project. The AWS Amplify CLI will enable us to build a powerful GraphQL API with just a simple schema. In the first part of this series we are creating a new workspace with an app and a shared lib to hold our backend config.
tags: angular, graphql, awsamplify, awsappsync
cover_image: https://thepracticaldev.s3.amazonaws.com/i/nldjxs5mj229lfezt8hg.png
series: Angular with NX multi-app project powered by an AWS Amplify GraphQL Backend
---

We are going to create a Todo app using [Angular](https://angular.io) with the help of

- [Nx](https://nx.dev) to help with a multi-app, multi-team project style
- [AWS Amplify CLI](https://aws-amplify.github.io/docs) to create a fully featured backend, completely managed in code (infrastructure as code)
- [AWS AppSync JavaScript SDK](https://github.com/awslabs/aws-mobile-appsync-sdk-js) An AppSync client using Apollo and featuring offline and persistence capabilities
- [AWS Amplify Angular](https://aws-amplify.github.io/docs/js/angular) Angular components and an Angular provider which helps integrate with the AWS-Amplify library

We are creating an offline-ready Todo-App backed by a GraphQL API and Authentication. We can expand on this app by adding further categories and features. But let's start simple.

You can take a look at the code [in this repo](https://github.com/BeaveArony/amplify-angular-nx-todo).

## Prerequisites

I'm assuming, that you have [NodeJS](https://nodejs.org) version 8 or higher installed and that you have access to an AWS Account. The free tier is very generous during the first year and will probably cost you nothing.

## Create a workspace

NX is a schematic on top of Angular CLI, that gives us tools to setup a workspace, where it is easy to share code between apps and to create boundaries to help working within a team or even multiple teams. 

By separating out logic into modules these can be reused easily by just importing them like we are importing other npm packages. We don't need to publish any of those packages to some public or private repository. This saves us some headaches about running tests across different versions of each library and provides us a snapshot in our repo of all the parts working together.

With the AWS Amplify CLI we can even connect one or more cloud stacks to our workspace, so that we get 'Infrastructure as Code' of our backend going along with our frontend apps. It's not in the sense of having completely different products all put into the same repo. Rather thinking in terms of having a customer web- and separate mobile app, an admin portal, an electron desktop app for the support team, a manager reports app and so on... They all work with the same data in a way and would immensely profit from shared SDKs and public interfaces across libraries.

Even if you end up using just one app, where you include different domains or Lines of Businesses (LOB), it helps to structure the code base so that teams can work at the same time without worrying too much about the painful integration into production later.

We start by creating a workspace folder by executing the following command using npx, so that we don't have to globally install it

```
npx @nrwl/schematics amplify-graphql-angular-nx-todo
```

When asked, we answer some questions

**_? Which stylesheet format would you like to use?_** CSS

**_? What is the npm scope you would like to use for your Nx Workspace?_** my

**_? What to create in the new workspace (You can create other applications and libraries at any point using 'ng g')_** empty

We named our workspace 'my' and it should be usually named after your organization or project, ie. 'myorg'.

We create an empty workspace, so that we can name our first application however we want. Otherwise it would get the name 'amplify-graphql-angular-nx-todo'.

## Create an app

We decide to call our first app 'todo-app' (and it will be the only one for this post).

```
ng g app todo-app
```

**_? What framework would you like to use for the application?_** Angular

**_? In which directory should the application be generated?_**

**_? Which stylesheet format would you like to use?_** CSS

**_? Which Unit Test Runner would you like to use for the application?_** Jest

**_? Which E2E Test Runner would you like to use for the application?_** Cypress

**_? Which tags would you like to add to the application? (used for linting)_** app

This created two apps

1. todo-app
2. todo-app-e2e

The second app is an end-to-end test. The app is our actual app and we will try to only use it for orchestrating other modules as much as possible. Most of our code we will put in libs to reuse it in possible further apps in the future.

## Create libs

Where do we put our code then? NX makes it easy to create new libs! We compose our app out of possibly many libs.

1. We need a lib for our backend stuff, let's call this lib `appsync`
2. We put our UI components into the lib `todo-ui`

So let's run the following commands to generate our desired libs:

```
ng g lib appsync --tags=shared --framework=angular --unitTestRunner=jest --style=css
ng g lib todo-ui --tags=ui --framework=angular --unitTestRunner=jest --style=css
```

## Create the AWS backend

The (AWS Amplify CLI)[https://github.com/aws-amplify/amplify-cli] makes it very easy to create a serverless backend in the AWS cloud. Everything is defined in code and can be checked in our source control. It elevates the need to write lengthy CloudFormation templates that describe our cloud resources. A few simple commands will spin up entire cloud stacks using team resources or creating one-of developer sand-boxes to try out new features.

Install the Amplify CLI globally (if haven't done so previously) by running those commands:

```
npm install -g @aws-amplify/cli
amplify configure
```

If you are completely new to AWS Amplify, [you should check out the docs of how to configure the Amplify CLI](https://aws-amplify.github.io/docs)

Here we actually install the AWS Amplify CLI globally instead of using npx! We will use the amplify command quite often.

Install the Amplify JavaScript dependencies

```
npm i aws-amplify aws-amplify-angular
```

## Adjusting the angular app to Amplify's SDK

There are a few things we need to change in order for us to use the AWS Amplify SDK in an Angular environment:

1. Tell typescript about the `window.global` variable
2. Add 'node' to tsconfig
3. Write a script to rename aws-exports.js to aws-exports.ts.

Open `apps/todo-app/src/polyfills.ts` and add the following line at the top of the file

```typescript
(window as any).global = window;
```

The `node` package needs to be included in `apps/todo-app/tsconfig.app.json`

```json
{
  "compilerOptions": {
    "types": ["node"]
  }
}
```

AWS Amplify comes with an UI-Library. We might just as well reuse the styles from those components and build upon them. Add the following lines to `apps/todo-app/src/styles.css`:

```css
/* You can add global styles to this file, and also import other style files */
@import '~aws-amplify-angular/theme.css';
```

We connect our project or branch to a cloud resource stack by running

```
amplify init
```

Using NX we need to change quite a few defaults:

**_? Enter a name for the project_** amplifynx
**_? Enter a name for the environment_** master
**_? Choose your default editor:_** Visual Studio Code
**_? Choose the type of app that you're building_** javascript
Please tell us about your project
**_? What javascript framework are you using_** angular
**_? Source Directory Path:_** libs/appsync/src/l
**_? Distribution Directory Path:_** dist/apps/todo-app
**_? Build Command:_** npm run-script build
**_? Start Command:_** npm run-script start
Using default provider awscloudformation

**_? Do you want to use an AWS profile?_** Yes
**_? Please choose the profile you want to use_** my.aws.profile
⠇ Initializing project in the cloud...

We named our cloud environment master, corresponding to our current git branch master. We can easily add further branches, like develop and create equally named environments (stacks) in the cloud.

We set the **_Source Directory Path_** to our previously created lib named _appsync_ so that the automatically generated file aws_exports.js will land in this folder by default. This way we can share our cloud config with different apps by just importing this lib.

The **_Distribution Directory Path_** points to the dist output of a single app, which is only important, if we use the amplify CLI commands to build or serve the app or if we connect our app to [Amplify Console](https://amplify.aws). The latter makes it very easy to create a CI/CD Pipeline by just connecting a GIT branch to a deployment.

You will notice a new top-level folder named `amplify`. Most of the files created in this folder were added to `.gitignore`. The rest of the files can and should be added to version control and can be shared with your team. If you plan to make your repo public you should also add `amplify/team-provider-info.json` to `.gitignore`.

## Let's add our first category `auth`!

```
amplify add auth
```

I like to change the default configuration to soften the password policy and provide a custom email subject text, but you can choose the defaults if you like and later change any of those settings by running

```
amplify auth update
```

To create these `auth` resources in the cloud run

```
amplify push
```

You will see

```
Current Environment: master

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Auth     | cognitonxtodo | Create    | awscloudformation |
? Are you sure you want to continue? (Y/n)
```

And after a while you will find the newly created Cognito user pool when you log in to your AWS console.  
This command will take you there:

```
amplify console auth
```

## Rename aws-exports.js

The `amplify push` command should have generated a file: `libs/appsync/src/lib/aws-exports.js`, which we should rename to `aws-exports.ts` to easily import the config in other typescript files. To automate this we use the scripts section in `package.json`. Now each time we either serve or build our app the file is renamed.

```json
{
  "start": "npm run rename:aws:config || ng serve; ng serve",
  "build": "npm run rename:aws:config || ng build --prod; ng build --prod",
  "rename:aws:config": "cd libs/appsync/src/lib && [ -f aws-exports.js ] && mv aws-exports.js aws-exports.ts || true"
}
```

We should also add this renamed file to `.gitignore`, so change `aws-export.js` to `aws-exports.*s`.

## Adding a GraphQL API

If you have ever written a GraphQL API before you will appreciate the simplicity of creating one by just typing a few lines:

```
amplify add api
```

**_? Please select from one of the below mentioned services_** GraphQL  
**_? Provide API name:_** amplifynx  
**_? Choose an authorization type for the API_** Amazon Cognito User Pool  
Use a Cognito user pool configured as a part of this project  
**_? Do you have an annotated GraphQL schema?_** No  
**_? Do you want a guided schema creation?_** Yes  
**_? What best describes your project:_** Single object with fields (e.g., “Todo” with ID, name, description)  
**_? Do you want to edit the schema now?_** Yes

This will open your previously configured editor with a sample Todo schema:

```
type Todo @model {
  id: ID!
  name: String!
  description: String
}
```

We don't want the description, but would like the _completed_ status of our todos to be tracked.
At first to not worry too much about offline and sorting in the backend we can add a _createdOnClientAt_ timestamp to sort the todos on the client. AppSync adds _createdAt_ and _updateAt_ timestamps by default, which we could use in the schema, but it will mess with our todo-list when we queue up several todos while we are offline and reconnect.

To explain this a little:


Say we only rely on the _createdAt_ property to sort and we add todo-1 and todo-2 while offline. These get added to our outbox. They are sorted correctly, because we add a timestamp when we create each of those todos. Once we are online, todo-1 will be sent to the server and will receive a new _createdAt_ timestamp on the server, since it was created there. This timestamp is at a later point in time than todo-2's timestamp. So todo-1 will jump to position 2 until todo-2 was transmitted. After todo-2 was sent to the server, getting a new timestamp, it will jump back to position 2 again. We will see a short reordering happen in our client.

The _createdOnClientAt_ property is not touched on the server.

Change the schema to this and save:

```
type Todo @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  completed: Boolean!
  createdOnClientAt: AWSDateTime!
}
```

We added `@auth(rules: [{allow: owner}])` to the Todo type to only show the todos of each logged in user. This will automatically add an _owner_ field to each todo in the underlying todo table in DynamoDB.

## What happened behind the scenes?

When you look into the `amplify/backend/api/{angularnx}/build` directory, you will find an oppiniated and enhanced GraphQL schema by the AWS Amplify team. It generated all types of CRUD operations and Input objects with filtering and pagination automatically! By just writing six lines of annotated graphql we get 116 lines of graphql best practices out of the box! That's just dandy!

## Upload the API

When we decide to push our API for the first time we get asked a few questions about generating code and where to place it. Choose _angular_ as language target and leave the rest as default!

```
amplify push
```

```
Current Environment: master

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Api      | amplifynx     | Create    | awscloudformation |
| Auth     | cognitonxtodo | No Change | awscloudformation |
? Are you sure you want to continue? Yes
```

**_? Do you want to generate code for your newly created GraphQL API_** Yes  
**_? Choose the code generation language target_** angular  
**_? Enter the file name pattern of graphql queries, mutations and subscriptions_** (src/graphql/**/_.graphql)  
_**? Enter the file name pattern of graphql queries, mutations and subscriptions**_ src/graphql/API.service.ts  
_**? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions\*\*\* Yes  
⠋ Updating resources in the cloud. This may take a few minutes...

> There is a bug when trying to use another directory, like our lib folder, so just leave the default paths `src/graphql/*`! Otherwise the service or types will end up in our lib and be pretty much empty and all *.graphql files will be in the default location.

## Amplify Code Generator

We get two choices when we decide to generate code:

1. angular
2. typescript

### 1. angular

If we choose angular as the target (which we do in our example), we get a single file with all the typescript types and an angular service. This service uses the amplify SDK to connect to the API. It's not really useful to us, since we decided to use the AppSync SDK as our client instead to connect to our cloud resources to get offline and persistance out of the box. We won't use the types nor the service directly, but the operations defined in the service can provide a good starting point to write our own queries and mutations.

When choosing _angular_ also generates `queries.graphql`, `mutations.graphql`, `subscriptions.graphql` and an introspection schema `schema.json`. This is very useful as we will see later on.

### 2. typescript

This generates \*.ts files with all queries, mutations and subscriptions exported as strings.  
It also generates a file `API.ts` that defines all the types.

I don't like this option as much as the other generators we will explore later. Here is an example generated by the _typescript_ option:

```typescript
export type CreateTodoMutation = {
  createTodo: {
    __typename: 'Todo';
    id: string;
    name: string;
    completed: boolean;
    createdOnClientAt: string;
  } | null;
};
```

It's correct to have the nested `createTodo` property, but to type our Todo objects in code we need to do this:

```typescript
const newTodo: CreateTodoMutation['createTodo'] = {
  /*...*/
};
```

My code editor will not help with the string part and it might introduce subtle errors in complex schemas.

## Apollo Code Generator

Another possibility is to use the apollo client's code generator. I like its generated types. They are clean, short and the above code would look like this

```typescript
const newTodo: CreateTodo_createTodo = {
  /*...*/
};
```

You can use it simply by executing

```
npx apollo-codegen generate src/graphql/*.graphql --schema src/graphql/schema.json --addTypename --target typescript --output libs/appsync/src/lib/api.types.ts
```

> Installing it locally and running it gives me an error, because of a version mismatch of the dependency _graphql_ used in _aws-appsync_. Using npx works great, though and I didn't worry about finding another solution for this. Using yarn might work better...

## GraphQL Code Generator

One very important feature I am looking for in a code generator is the possibility to customize how the code is generated. [{ GraphQL } code generator](https://graphql-code-generator.com) is such a tool. It uses plugins to generate code for lots of different languages.

You could install it by running

```
npm i graphql-code-generator graphql-codegen-typescript-common graphql-codegen-typescript-client
graphql-codegen-fragment-matcher
```

Create a codegen.yml file:

```yml
overwrite: true
schema: src/graphql/schema.json
documents: src/graphql/*.graphql
generates:
  ./libs/appsync/src/lib/graphql.ts:
    plugins:
      - 'typescript-common'
      - 'typescript-client'
      - 'fragment-matcher'
```

and simply run

```
npm run gql-gen
```

This will generate a graphql.ts file in our lib providing all the types organized in namespaces. If we would add another plugin targeting _angular_ we would also get a service for each operation, that we could simply inject in our components.

> Note, we will NOT be using this tool in our app

## Choosing the code generator

I think using the apollo code generator is probably the middle choice. It's easy enough to use when you add it the `package.json`'s scripts section and hook it up to the amplify push command:

```json
{
  "generate": "npx apollo-codegen generate src/graphql/*.graphql --schema src/graphql/schema.json --addTypename --target typescript --output libs/appsync/src/lib/api.types.ts",
  "push": "amplify push && npm run generate"
}
```

> Make sure to use `angular` (not `typescript`) as the target in the previous step when configuring amplify's codegen, so that the `src/graphql/*.graphql` files are generated.

> The `src/graphql` folder is only used for code generation and should not be imported anywhere in our app!

Since everything in `src/graphql` is generated, we can also put it in `.gitignore` together with `.graphqlconfig.yml`.

```
src/graphql
.graphqlconfig.yml;
```

## Writing our GraphQL operations

Our app needs to
1. Fetch our list of todos
2. Create a new todo
3. Update the completed status of a todo
4. Delete a todo

To make our operations reusable in different components or services, we put them in a file `libs/appsync/src/lib/gql-operations.ts`.

```typescript
import gql from 'graphql-tag';

export const CREATE_TODO = gql`
  mutation CreateTodo($input: CreateTodoInput!) {
    createTodo(input: $input) {
      __typename
      id
      name
      completed
      createdOnClientAt
    }
  }
`;

export const LIST_TODOS = gql`
  query ListTodos($filter: ModelTodoFilterInput, $nextToken: String) {
    listTodos(filter: $filter, limit: 999, nextToken: $nextToken) {
      __typename
      items {
        __typename
        id
        name
        completed
        createdOnClientAt
      }
    }
  }
`;

export const UPDATE_TODO = gql`
  mutation UpdateTodo($input: UpdateTodoInput!) {
    updateTodo(input: $input) {
      __typename
      id
      name
      completed
      createdOnClientAt
    }
  }
`;

export const DELETE_TODO = gql`
  mutation DeleteTodo($input: DeleteTodoInput!) {
    deleteTodo(input: $input) {
      __typename
      id
    }
  }
`;
```

> To not worry about fetching more todos and simplifying apollo's caching, we hard code the limit in listTodos to 999

Don't forget, that we have auto-generated code in `src/graphql/API.service.ts` so we can simply copy some of those statement and use them as our starting point. In a more advanced app with a deeply nested schema the generated operations will need to be adjusted on a per component need.

From a software architecture's point of view it would be better to create a new library called `todo-data` or `todo-model` and place the file there naming it `todo-operations.ts`. Then we could also add a facade which will be the only thing we would expose to the other libs. This data layer lib could also hold our local state using redux for example. Going this route would make our components simpler. We might expand on this in the future.

## Setup the AppSync Client

Install the client, graphql-tag and optionally localforage

```
npm i aws-appsync graphql-tag localforage
```

It's time to modify `libs/appsync/src/lib/appsync.module.ts` to instantiate and configure the AppSync client.

Change the content of the file to this:

```typescript
import { NgModule } from '@angular/core';
import Amplify, { Auth } from 'aws-amplify';
import { AmplifyAngularModule, AmplifyService } from 'aws-amplify-angular';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';
import * as localForage from 'localforage';
import config from './aws-exports';

Amplify.configure(config);

export const AppSyncClient = new AWSAppSyncClient({
  url: config.aws_appsync_graphqlEndpoint,
  region: config.aws_appsync_region,
  auth: {
    type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
    jwtToken: async () =>
      (await Auth.currentSession()).getIdToken().getJwtToken()
  },
  complexObjectsCredentials: () => Auth.currentCredentials(),
  cacheOptions: { addTypename: true },
  offlineConfig: { storage: localForage }
});

@NgModule({
  exports: [AmplifyAngularModule],
  providers: [AmplifyService]
})
export class AppsyncModule {}
```

We are creating the AWSAppSyncClient and exporting the instance, so we can use it elsewhere.
AmplifyService needs to be added to the providers array, since it does not use `@Injectable({providedIn: 'root'})`.

To persist our the AppSync store in IndexedDB rather than LocalStorage, we use the library localforage.

To hook it up, don't forget to add `AppsyncModule` to the imports array of `apps/todo-app/src/app/app.module.ts`.

> Note: We are not using the [Apollo-Angular](https://github.com/apollographql/apollo-angular) client here. This would give us an injectable service we could use throughout our app and which wraps the underlying zen-observables with rxjs. This is not necessary though, as we will see building this app in angular.

> AWSAppSyncClient uses quite a few libraries under the hood, which makes it difficult to exchange or add some of them. I haven't found a way to use [apollo-link-state](https://github.com/apollographql/apollo-link-state) and [apollo-angular-link-http](https://www.apollographql.com/docs/angular/basics/network-layer.html) together with AWSAppSyncClient while retaining all TypScript typings.
This means NO Angular HttpClient and features like interceptors when working directly with the AWSAppSyncClient!

## Offline helpers

The AWS AppSync SDK offers [helpers](https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/OFFLINE_HELPERS.md) to build mutation objects to use with apollo-client. This makes it quite easy to work with an optimistic UI in bad network conditions and update the local InMemoryCache when changes happen in our app.

There are higher level helpers available for react, but when using other libraries, we need to use the lower level function `buildMutation`. This function needs an apollo-client instance for example, which we can abstract away in our own typescript functions. Since we have generated types for our API we can also use these to give us additional type safety when providing `__typename` strings.

I also find it easier to use a config object instead of using a long parameter list. Here are my TypeScript Offline Mutation Builders which you can put in a file `libs/appsync/src/lib/offline.helpers.ts`:

```typescript
import { OperationVariables } from 'apollo-client';
import {
  buildMutation,
  CacheOperationTypes,
  CacheUpdatesOptions,
  VariablesInfo
} from 'aws-appsync';
import { DocumentNode } from 'graphql';
import { AppSyncClient } from './appsync.module';

export interface TypeNameMutationType {
  __typename: string;
}

export interface GraphqlMutationInput<T, R extends TypeNameMutationType> {
  /** DocumentNode for the mutation */
  mutation: DocumentNode;
  /** An object with the mutation variables */
  variablesInfo: T | VariablesInfo<T>;
  /** The queries to update in the cache */
  cacheUpdateQuery: CacheUpdatesOptions;
  /** __typename from your schema */
  typename: R['__typename'];
  /** The name of the field with the ID (optional) */
  idField?: string;
  /** Override for the operation type (optional) */
  operationType?: CacheOperationTypes;
}

/**
 * Builds a MutationOptions object ready to be used by the ApolloClient to automatically update the cache according to the cacheUpdateQuery
 * parameter
 */
export function graphqlMutation<
  T = OperationVariables,
  R extends TypeNameMutationType = TypeNameMutationType
>({
  mutation,
  variablesInfo,
  cacheUpdateQuery,
  typename,
  idField,
  operationType
}: GraphqlMutationInput<T, R>) {
  return buildMutation<T>(
    AppSyncClient,
    mutation,
    variablesInfo,
    cacheUpdateQuery,
    typename,
    idField,
    operationType
  );
}

export function executeMutation<
  T = OperationVariables,
  R extends TypeNameMutationType = TypeNameMutationType
>(mutationInput: GraphqlMutationInput<T, R>) {
  return AppSyncClient.mutate(graphqlMutation<T, R>(mutationInput));
}
```

> Note, this might break with future versions of the appsync sdk

## Export everything from our lib

To make it possible to import our types, graphql operations, helpers, appsync-client and amplify from our barrel we need to export them in the `libs/appsync/src/index.ts` file.

```typescript
export * from './lib/appsync.module';
export * from './lib/api.types';
export * from './lib/gql-operations';
export * from './lib/offline.helpers';
```

To import them in our components we can simply write
```typescript
import { /* ... */ } from '@my/appsync';
```

## Recap

So far, we have setup our Angular multi-app workspace with NX, initialized AWS Amplify to give us a managed authentication service using AWS Cognito and generated a serverless GraphQL API using AWS AppSync. Just with a few CLI commands!

To make it possible to reuse our backend in more than one app, we imported and configured everything in a separate custom library.

Since GraphQL works with a schema we could even generate all typings automatically and copy query and mutation operations as a starting point for our own operational needs.

We also wrote some utility functions to help with the Apollo client-side cache in offline scenarios.

## What to expect in part 2

In [part 2](https://dev.to/beavearony/creating-angular-ui-components-and-connecting-them-to-the-graphql-appsync-api-part-2-33n0) we will create a UI and use the stuff we created here to connect to our backend.

{% link beavearony/creating-angular-ui-components-and-connecting-them-to-the-graphql-appsync-api-part-2-33n0 %}