# Getting started

### Who is this for?

* People who have been hacking away a few applications and are now looking to achieve professional quality.
* Solo founders who wants to build a high quality SaaS product.

### What does it cover?

Everything you need to know to be able to build, run, scale and maintain a web application.

* How to build the most common features
* Automate code quality check
* Unit, Integration and E2E tests
* Deploy apps on AWS
* Secure your API, server and database
* Take payment for a monthly subscription

### What are we building?

![](/files/-LrANZl5eN4aUdaGntLT)

A collaborative bookmarking SaaS application with the following features:

* User sign in/up
* Add, update and delete links
* Organise your links with tags
* Add and remove collaborators
* Realtime comments

To achieve that we will:

* Build a [GraphQL](https://graphql.org) API with [Subscriptions](https://www.apollographql.com/docs/react/advanced/subscriptions/), package it in a [Docker](https://www.docker.com) container and deploy it to [Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/).
* Build a [React](https://reactjs.org) application using [Styled-Components](https://www.styled-components.com) and [Apollo](https://www.apollographql.com) and host it on [S3](https://aws.amazon.com/s3/)**.**
* Store our data in [Postgres](https://www.postgresql.org) managed by [RDS](https://aws.amazon.com/rds/) in a [Private VPC](https://aws.amazon.com/vpc/).
* Use [CloudFront](https://aws.amazon.com/cloudfront/) to enable HTTPS, serve S3 files and redirect URLs.
* Buy a domain on [Route53](https://aws.amazon.com/route53/)
* Manage our cloud resources with [CloudFormation](https://aws.amazon.com/cloudformation/).

### Resources to read before you start

* [Introduction To JavaScript](https://www.codecademy.com/learn/introduction-to-javascript)
* [Learn CSS](https://www.codecademy.com/learn/learn-css)
* [React tutorial](https://reactjs.org/tutorial/tutorial.html)
* [How do I start with Node.js after I installed it?](https://nodejs.org/en/docs/guides/getting-started-guide/)
* [GitHub guide](https://guides.github.com/activities/hello-world/)
* [TypeScript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)
* [Getting started with Docker](https://docs.docker.com/get-started/)

You don't need in-depth knowledge about those technologies to follow the tutorial.

{% hint style="info" %}
Any questions? Find me on Twitter [@florianherrengt](https://twitter.com/florianherrengt).

Want to hire us? [Let's chat.](https://calendly.com/florianherrengt/15min)&#x20;
{% endhint %}


# Project setup


# Requirements

* [Node](https://nodejs.org/)
* [Git](https://git-scm.com)
* [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/)
* [Docker](https://www.docker.com)
* Unix system recommended (Mac or Linux) but Windows should work fine.


# Files organisation

Most SaaS projects are made of 2 layers; frontend and backend. In our case, React and Node. There are 2 common ways to organise our project:

### **Divide and rule:** One repository for each layer

The Node and React app will live in separated repositories. You will have to checkout, pull, commit and push accordingly.

This setup makes sense for big teams. Each team has specialised skills and is dealing with a specific part of the project.

For smaller teams though, it just slows things down. When you're building your product, most of the time, the frontend relies on a change from the backend. The frontend needs to wait for this backend change to be approved, merged and deployed.

### One repo to rule them all: Mono repository

Mono repositories work well in this scenario because everything needs to be built. There is no clear line between frontend and backend work. We are focused on features, not the code. Backend or frontend? It doesn't matter. It all has to be done.

When working on a feature, you build whatever is required to ship this feature. Most of the time, it will look something like:

* Create an endpoint to interact with the database
* Build a new UI form to display and manipulate the data
* Write tests

It is easier if everything is in the same place. When the feature is ready, create a pull request. Once merged and deployed, move on to the next feature.

Later, when the team or the app gets bigger, you can always move parts to another repo.

### Structure

In our example, we will use a single repository with separate folders for the frontend and backend, named `web` and `api` respectively.

Later, the project could have a [React Native](https://facebook.github.io/react-native/) app in `mobile` or a queue system in `workers`.

```
├── README.md
├── node_modules
├── package.json
├── packages
│   ├── api
│   │    ├── node_modules
│   │    └── package.json
│   └── web
│   │    ├── node_modules
│   │    └── package.json
├── tslint.json
├── tslint.production.json
└── yarn.lock
```


# Lerna

### Setup the project with one command

When I first had a look at [Lerna](https://github.com/lerna/lerna), I couldn't understand how it works and what was its value. Essentially, what would have been a repository is now a folder in `packages`. All the dependencies will be installed with `lerna bootstrap`.

What does this mean? When you clone the monorepo, you will get all the code required to run the app. When you run `yarn install` you'll install all the dependencies.

### Setting up Lerna

Create `lerna.json`

{% code title="lerna.json" %}

```javascript
{
  "packages": ["packages/**"],
  "version": "independent",
  "npmClient": "yarn"
}
```

{% endcode %}

Install Lerna **at the root of the project**.

```
$ yarn add lerna
```

### Let's try

Go to the `packages/api` and install `express`

```
$ cd packages/api
$ yarn add express
```

The `express` dependency should have been added to `packages/api/package.json`

{% code title="packages/api/package.json" %}

```javascript
  "dependencies": {
    "express": "^4.17.1"
  }
```

{% endcode %}

Let's do the same for `packages/web` and add `react`

```
$ cd packages/web
$ yarn add react
```

We should now have `node_modules` folders in `api` and `web`.

```
$ ls packages/api/node_modules # You should see express
$ ls packages/web/node_modules # You should see react
```

Let's try to delete both `node_modules` folders and bootstrap from the project's root directory. It should install all dependencies.

```
$ lerna bootstrap
```

{% hint style="info" %}
Don't forget to add `node_modules` to `.gitignore`.
{% endhint %}

### Post install

We can add anything in `scripts` of our `package.json` and run it with `yarn name-of-script`.

Let's try this first with our `package.json` from the root directory, :

{% code title="package.json" %}

```javascript
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "name-of-script": "echo my script"
},
```

{% endcode %}

```
$ yarn name-of-script
yarn run v1.15.2
$ echo my script
my script
✨  Done in 0.06s.
```

There are pre-defined keywords for `npm-scripts`. You can find all of them [here](https://docs.npmjs.com/misc/scripts) but we are interested in `postinstall`.

```
postinstall: Run AFTER the package is installed
```

We can add our bootstrap step here:

{% code title="package.json" %}

```javascript
"scripts": {
    "postinstall": "lerna bootstrap"
},
```

{% endcode %}

We can now install all the dependencies from the root directory:

```
$ yarn
[1/4] 🔍  Resolving packages...
$ lerna bootstrap
lerna notice cli v3.16.4
lerna info versioning independent
lerna info Bootstrapping 2 packages
lerna info Installing external dependencies
lerna info Symlinking packages and binaries
lerna success Bootstrapped 2 packages
✨  Done in 1.75s.
```

### Final files and folders structure

```
.
├── lerna.json
├── node_modules
├── package.json
├── packages
│   ├── api
│   │   ├── package.json
│   │   ├── node_modules
│   │   └── yarn.lock
│   └── web
│       ├── package.json
│       ├── node_modules
│       └── yarn.lock
└── yarn.lock
```

{% hint style="info" %}
[`lerna`](https://github.com/florianherrengt/book-code/tree/lerna) branch available on GitHub.
{% endhint %}


# Linter

A linter is the equivalent of a grammar check for code. It doesn't mean the code is correct, but it helps maintain consistency across the code base.

For this project, we will be using [TSLint](https://palantir.github.io/tslint/) for Typescript.

Let's start by adding `typescript` and `tslint` and creating a `tslint.json` file at the root of the project.

```
$ yarn add tslint typescript
$ tslint --init
```

Create a file in `packages/api/index.ts` with invalid code structure:

{% code title="packages/api/index.ts " %}

```typescript
var test = function () {
    return 'hello world'
}
```

{% endcode %}

```
$ tslint packages/api/index.ts

ERROR: packages/api/index.ts:1:1 - Forbidden 'var' keyword, use 'let' or 'const' instead
ERROR: packages/api/index.ts:1:12 - non-arrow functions are forbidden
ERROR: packages/api/index.ts:1:20 - Spaces before function parens are disallowed
ERROR: packages/api/index.ts:2:12 - ' should be "
ERROR: packages/api/index.ts:2:25 - Missing semicolon
ERROR: packages/api/index.ts:3:2 - file should end with a newline
ERROR: packages/api/index.ts:3:2 - Missing semicolon
```

Have a look at [all the available rules](https://palantir.github.io/tslint/rules/).

### Project specific configuration

The Node API and React app `tslint.json` configuration could be different. We can share common rules between the project from the root.

```
$ cd packages/api
yarn add tslint typescript
tslint --init
```

Replace the content of `packages/api/tslint.json` with:

{% code title="packages/api/tslint.json" %}

```typescript
{
    "extends": ["../../tslint.json"]
}

```

{% endcode %}

You can do the same with `web` but we'll come back to it later.

{% hint style="info" %}
[`linter`](https://github.com/florianherrengt/book-code/tree/linter) branch available on GitHub.
{% endhint %}


# Prettier

Remaining consistent isn't easy when the team grows. A lot of code formatting issues brought up by the linter can be fixed automatically. That's Prettier.

```
$ yarn add prettier
```

Change `index.ts` to following code:

{% code title="index.ts" %}

```typescript
const printFizz = number => {
        if (!Boolean(number % 3)) 
        {
return "fizz";
    }
};

```

{% endcode %}

then run

```
$ prettier --write packages/api/index.ts
```

The file has been formatted correctly:

{% code title="index.ts" %}

```typescript
const printFizz = number => {
    if (!Boolean(number % 3)) {
        return "fizz";
    }
};
```

{% endcode %}

### Avoid TSLint conflict

```
yarn add tslint-config-prettier -D
```

Update `tslint.json`

{% code title="tslint.json" %}

```typescript
{
  "defaultSeverity": "error",
  "extends": ["tslint:recommended", "tslint-config-prettier"],
  "jsRules": {},
  "rules": {},
  "rulesDirectory": []
}
```

{% endcode %}


# GitHook

We are going to use the git `pre-commit` [hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) to make sure the code gets *prettified* before committing it.

### Install Husky

> [Husky](https://github.com/typicode/husky) can prevent bad `git commit`, `git push` and more 🐶 *woof!*

```
$ yarn add husky pretty-quick -D
```

Add the following to the root `package.json`

{% code title="package.json" %}

```typescript
"husky": {
  "hooks": {
    "pre-commit": "pretty-quick --staged"
  }
}
```

{% endcode %}

Make sure husky is install

```
$ node node_modules/husky/lib/installer/bin install
```

Add it to `postinstall` to automate the process:

{% code title="package.json" %}

```typescript
"scripts": {
    "postinstall": "lerna bootstrap && node node_modules/husky/lib/installer/bin install"
}
```

{% endcode %}

When we commit, we should now see a message similar to this one:

```
🔍  Finding changed files since git revision 5053732.
🎯  Found 2 changed files.
✍️  Fixing up packages/api/index.ts.
✅  Everything is awesome!
```

{% hint style="info" %}
[`prettier`](https://github.com/florianherrengt/book-code/tree/prettier)branch available on GitHub.
{% endhint %}


# Testing

### Setting up Jest with TypeScript

To keep thing clear and simple, we will move all the code related to the app to a new `src` folder. For now, we just have `index.ts`, but the number of files will quickly grow.

```
$ cd packages/api
$ mkdir src
$ mv index.ts src
```

Create `jest.config.js` in `packages/api` with the following content:

{% code title="jest.config.js" %}

```typescript
module.exports = {
  preset: 'ts-jest',
  rootDir: 'src',
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  testEnvironment: 'node',
  moduleFileExtensions: ['js', 'ts', 'tsx'],
  moduleDirectories: ['node_modules'],
  coverageReporters: ['html'],
  setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
  globals: {
    'ts-jest': {
      tsConfig: 'tsconfig.json',
    },
  },
};
```

{% endcode %}

Create `setupTests.ts` and leave it empty. Anything in this file is run before the tests. We will need it later.

Now is also a good time to create our `tsconfig.json` file in `packages/api`.

```
$ cd packages/api
$ tsc --init
$ yarn add esnext
```

Replace the content with the following:

{% code title="tsconfig.json" %}

```javascript
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "sourceMap": true,
    "declaration": true,
    "strictNullChecks": true,
    "noUnusedLocals": false,
    "allowUnusedLabels": false,
    "noUnusedParameters": false,
    "pretty": true,
    "skipLibCheck": true,
    "lib": ["es2015", "esnext.asynciterable"]
  },
  "sourceMap": true,
  "outDir": ".build",
  "moduleResolution": "node",
  "rootDir": "./src"
}
```

{% endcode %}

### Writing tests

There are 2 things that are notoriously difficult when unit testing:

1. Mocking imports
2. Testing asynchronous code

Let's write a function to fetch all the emojis available on GitHub.

First, install the required dependencies

```
$ cd packages/api
$ yarn add node-fetch @types/node-fetch
$ yarn add jest @types/jest ts-jest -D
```

Fetch the emojis:

{% code title="index.ts" %}

```typescript
import fetch from "node-fetch";

const getEmoji = async (emoji: string): Promise<string> => {
    const response = await fetch("https://api.github.com/emojis");
    const data = await response.json();
    return data[emoji];
};

export { getEmoji };
```

{% endcode %}

Now we have a problem, how do we mock the request to GitHub? We don't want to send the request each time we run the test.

### Mocking dependencies is hard

A lot of people struggle with setting up all the mocking required to isolate a function. They would end up writing a large amount of code to setup the test, but little to test the actual function.

### Dependencies injection

Injecting dependencies allows us to arbitrarily set a dependency at any time. This is perfect for unit testing. We can also leverage some TypeScript functionalities around classes.

Let's get started:

```
$ yarn add typedi
```

Create a new file  `Emoji.ts`with a class `Emoji`

{% code title="Emoji.ts" %}

```typescript
import fetch from "node-fetch";
import { Container } from "typedi";

class Emoji {
  private readonly _fetch: typeof fetch = Container.get("fetch");
  async get(emoji: string): Promise<string> {
    const response = await this._fetch("https://api.github.com/emojis");
    const data = await response.json();
    return data[emoji];
  }
}

export { Emoji };
```

{% endcode %}

By using `private readonly _fetch: typeof fetch = Container.get("fetch");` we can now set `fetch` to anything we'd like.

{% code title="Emoji.spec.ts" %}

```typescript
import { Emoji } from "./Emoji";
import { Container } from "typedi";

describe("fetchRepositories function", () => {
  it("should fetch and return emoji passed as a param", async () => {
    // Arrange: setup everything we need
    const computer = "mocked url";
    const data = { computer };
    const json = jest.fn().mockResolvedValue(data);
    const fetch = jest.fn(() => ({ json }));
    Container.set("fetch", fetch);

    const emoji = new Emoji();

    // Act: execute the code we want to test
    const result = await emoji.get("computer");

    // Assert: check if it worked
    expect(result).toStrictEqual(computer);
  });
});

```

{% endcode %}

{% hint style="info" %}
Structure your tests with 3 well-separated sections: Arrange, Act & Assert.
{% endhint %}

### Containers file

To avoid future mistake, we will create a `containers.ts` file to list all the containers available and their type.

{% code title="containers.ts" %}

```typescript
import fetch from "node-fetch";
import { Container } from "typedi";

enum ContainerNames {
    fetch = 'fetch'
}

export const setFetch = (fn: typeof fetch) => Container.set(ContainerNames.fetch, fn)
export const getFetch = (): typeof fetch => Container.get(ContainerNames.fetch)
```

{% endcode %}

And we can now use it in our `Emoji.ts` class

{% code title="Emoji.ts" %}

```typescript
import { getFetch } from './containers'

class Emoji {
    private readonly _fetch = getFetch()
    async get(emoji: string): Promise<string> {
        const response = await this._fetch("https://api.github.com/emojis");
        const data = await response.json();
        return data[emoji];
    }
}

export { Emoji };
```

{% endcode %}

That's it. We can now mock anything, anywhere in our code.

### Code coverage

If you run `jest index.spec.ts --coverage` you will get a new folder `src/coverage`

Open `coverage/index.html` in your browser.

![](/files/-LndtnnkzTwG018S3nA0)

`Emoji.ts` has 100% code coverage. Let's add a `if` statement and see what happens.

{% code title="Emoji.ts" %}

```typescript
import { getFetch } from './containers'

class Emoji {
    private readonly _fetch = getFetch()
    async get(emoji: string): Promise<string> {
        const response = await this._fetch("https://api.github.com/emojis");
        const data = await response.json();
        if (!data[emoji]) {
            // this is not covered by any test
            return 'emoji not found';
        }
        return data[emoji];
    }
}

export { Emoji };
```

{% endcode %}

Run `jest index.spec.ts --coverage` again

![](/files/-LnducsM6kSjP1xdYgh6)

There are no tests going though this `if` statement.

![](/files/-LnduscYMsa7TBxnGLxu)

This is a fantastic tool to see how much of your code is tested.

{% hint style="info" %}
Add `coverage` to `.gitignore`
{% endhint %}

### Why do we even write test?

Some people struggle to understand why unit tests are useful. So why should we write test?

#### It is documentation

If anything, by reading the list of "it should..." someone can quickly get an understanding of what this class is about. This someone could be you in a few months.

#### If it can break, it will break

Sometimes we get distracted, we make mistakes and we break things. That's why it's important to have a few basic tests to automatically check if everything still does what it's supposed to do.

#### It can be part of the debugging process

Instead of restarting the server, sending a request and looking at the result, you can write a test. You can setup the scenario, call the function and assert the result. It speeds up the feedback loop.

#### Refactoring becomes easier

If you spot something you don't like and want to improve, go ahead. Assuming there's good coverage, if something breaks, tests will catch it.

### To test or not to test

So people believe they must achieve 100% code coverage at all cost. While tests are great most of the time, writing tests for the sake of it is counter-productive.\
Test what makes sense. If something breaks, write a test so it doesn't happen again. With experience, you'll develop a sense for what needs to be tested and what doesn't.

The last thing you want to do is making your codebase more complex to test unimportant parts.

Test and/or mock when it makes sense.

{% hint style="info" %}
[`testing`](https://github.com/florianherrengt/book-code/tree/testing) branch available on GitHub
{% endhint %}


# Conclusion

We are now ready to start writing code. We have setup a project with professional standards, ready to onboard a team.

We covered the following topics:

* Knowledge and software required
* How the project is organised
* Install all dependencies with one line
* Enforce code style
* Writing tests

{% hint style="info" %}
We'd love to hear your feedback. Find me on Twitter [@florianherrengt](https://twitter.com/florianherrengt).
{% endhint %}


# Backend


# Files organisation

To keep things well structured, we will slip our code in different folders in `packages/api`.

* `/` project configuration files
* `src` all the code we write for the app.
  * `config` one file per environment.
  * `entities` our data models.
  * `migrations` if we need to alter the database.
  * `routers` one folder per route.

This is the final files structure:

```
.
├── jest.config.js
├── package.json
├── src
│   ├── app.ts
│   ├── config
│   │   ├── ci.ts
│   │   ├── index.ts
│   │   ├── local.ts
│   │   ├── production.ts
│   │   ├── shared.ts
│   │   └── staging.ts
│   ├── containers.ts
│   ├── coverage
│   │   ├── Emoji.ts.html
│   │   ├── base.css
│   │   ├── containers.ts.html
│   │   ├── index.html
│   │   ├── prettify.css
│   │   └── sort-arrow-sprite.png
│   ├── entities
│   │   ├── Link.ts
│   │   ├── User.ts
│   │   └── index.ts
│   ├── index.ts
│   ├── middlewares
│   │   ├── RateLimiter
│   │   │   ├── index.spec.ts
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── migrate.ts
│   ├── migrations
│   │   └── 001_example.ts
│   ├── routers
│   │   ├── Emoji
│   │   │   ├── index.spec.ts
│   │   │   └── index.ts
│   │   ├── GraphQL
│   │   │   ├── helpers
│   │   │   │   ├── PaginatedArgs.ts
│   │   │   │   ├── PaginatedResponse.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   └── resolvers
│   │   │       ├── Health.ts
│   │   │       ├── Link
│   │   │       │   ├── Link.spec.ts
│   │   │       │   ├── Link.ts
│   │   │       │   ├── LinkInput.ts
│   │   │       │   └── index.ts
│   │   │       ├── User
│   │   │       │   ├── User.spec.ts
│   │   │       │   ├── User.ts
│   │   │       │   ├── UserAuthInput.ts
│   │   │       │   └── index.ts
│   │   │       └── index.ts
│   │   └── index.ts
│   ├── sequelize.ts
│   └── setupTests.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
```


# Environment config

It is common to have multiple environments. For this project, we will have:

* **Local** to run on our machine for development.
* **CI** for automated tests.
* **Staging** to check if everything is working in the cloud. This can also be used to show a feature to someone before pushing it live.
* **Production** where we host our app.

{% hint style="info" %}
Try to keep all environments as similar as possible to each other.
{% endhint %}

### Config folder

Let's create a folder that contains the configurations for each environment. We will also have a `shared.ts`file for things common to all environment and `index.ts` to return the correct object.

```
.
└── config
    ├── ci.ts
    ├── index.ts
    ├── local.ts
    ├── production.ts
    ├── shared.ts
    └── staging.ts
```

{% tabs %}
{% tab title="index.ts" %}

```typescript
import { SharedConfig } from "./shared";
import { local } from "./local";
import { ci } from "./ci";
import { staging } from "./staging";
import { production } from "./production";

export interface Config extends SharedConfig {
  env: "local" | "ci" | "staging" | "production";
}

export const config: Config = { local, ci, staging, production }[
  process.env.CONFIG_ENV || "local"
];
```

{% endtab %}

{% tab title="shared.ts" %}

```typescript
export interface SharedConfig {
  logLevel: string;
}

export const sharedConfig = {
  logLevel: process.env.LOG_LEVEL || "info" // we will come back to logs later
};
```

{% endtab %}

{% tab title="local.ts" %}

```typescript
import { sharedConfig } from "./shared";
import { Config } from "./index";

export const local: Config = {
  ...sharedConfig,
  env: "local"
};
```

{% endtab %}

{% tab title="ci.ts" %}

```typescript
import { sharedConfig } from "./shared";
import { Config } from "./index";

export const ci: Config = {
  ...sharedConfig,
  env: "ci"
};
```

{% endtab %}

{% tab title="staging.ts" %}

```typescript
import { sharedConfig } from "./shared";
import { Config } from "./index";

export const staging: Config = {
  ...sharedConfig,
  env: "staging"
};
```

{% endtab %}

{% tab title="production.ts" %}

```typescript
import { sharedConfig } from "./shared";
import { Config } from "./index";

export const production: Config = {
  ...sharedConfig,
  env: "production"
};
```

{% endtab %}
{% endtabs %}

For now, there isn't much difference between our files. It will grow as we start adding features.

> Note that we use `CONFIG_ENV` instead of `NODE_ENV`. This is because some libraries and node itself behave differently when you set `NODE_ENV=production`. We want our environment to be as close as possible to each other.

Let's try this out:

{% code title="index.ts" %}

```typescript
import { config } from './config';

console.log(config);
```

{% endcode %}

```
$ cd packages/api
```

```
$ CONFIG_ENV=staging LOG_LEVEL=error ts-node src/index.ts
{ logLevel: 'error', env: 'staging' }
```

```
$ CONFIG_ENV=ci LOG_LEVEL=debug ts-node src/index.ts
{ logLevel: 'debug', env: 'ci' }
```

{% hint style="danger" %}
Never commit secrets to git. Always use environment variable.
{% endhint %}

### Making it work on Windows

We want each member of the team to be able to work in the environment they are the most comfortable. Most developers in the industry are using MacOS but this doesn't mean we should ignore other operating system.

The [cross-env](https://github.com/kentcdodds/cross-env) library allows us to run the same command on Unix and Windows.

```
$ cd packages/api
```

```
$ yarn add cross-env
```

This should now work everywhere:

```
$ cross-env CONFIG_ENV=ci LOG_LEVEL=debug ts-node src/index.ts
```

### DotEnv file

As our app growths, we will have to set more and more environment variables.\
To keep this clean, we will store them in a `.env` file.

{% code title="packages/api/.env" %}

```
CONFIG_ENV=local
LOG_LEVEL=debug
```

{% endcode %}

```
$ cd packages/api
```

```
$ yarn add dotenv
```

{% code title="index.ts" %}

```typescript
require('dotenv').config();
import { config } from './config';

console.log(config);
```

{% endcode %}

```
$ ts-node src/index.ts
{ logLevel: 'debug', env: 'local' }
```

{% hint style="warning" %}
Don't forget to add `.env` to `.gitignore` as it will contain secrets.
{% endhint %}

{% hint style="info" %}
[`environment`](https://github.com/florianherrengt/book-code/tree/environment) branch available on GitHub.
{% endhint %}


# Express API

[Express](https://expressjs.com) is a minimal and flexible Node.js web application framework. We will be using it to build our API.

Let's create an endpoint to get an emoji.

Here's the list of what we have to do:

* Delete`Emoji.ts` and `Emoji.spec.ts`.
* Install `express`
* Create a `routers` folder to keep our files organised
* Create a new file`/routers/emoji`
* Create a new file `app.ts`
* Create a new file `index.ts` (our main file)
* Update our config with `shared.ts` and `.env` to add `port`.
* To simplify imports, add`index.ts` exporting all the relevant files to each folder.

We will create the following file structure

```
.
├── app.ts
├── index.ts
└── routers
    ├── Emoji
    |   ├── index.spec.ts
    |   └── index.ts
    └── index.ts
```

{% code title="\~/packages/api" %}

```
$ yarn add express @types/express
```

{% endcode %}

To keep things clean, each top-level endpoint will have a router. \
The router defines the `GET, POST, PUT` and `DELETE`.

{% tabs %}
{% tab title="routers/emoji/router.ts" %}

```typescript
import { Router, Request, Response } from "express";
import { getFetch } from "../../containers";

export class EmojiRouter {
  public readonly router = Router();
  private readonly _fetch = getFetch();
  constructor() {
    this.router.get("/:emoji", this.get);
  }
  get = async (request: Request, response: Response) => {
    const { emoji } = request.params;
    const jsonResponse = await this._fetch("https://api.github.com/emojis");
    const data = await jsonResponse.json();
    if (!data[emoji]) {
      return response.status(404).json({ error: "emoji not found" });
    }
    return response.json({ emoji: data[emoji] });
  };
}
```

{% endtab %}

{% tab title="routers/index.ts" %}

```typescript
export * from "./emoji";
```

{% endtab %}
{% endtabs %}

We can test any method by mocking `request` and `response` using `jest.fn().mockReturnValue()`.

{% code title="routers/emoji/index.spect.ts" %}

```typescript
import fetch from "node-fetch";

import { EmojiRouter } from "./index";
import { setFetch } from "../../containers";
import { Request, Response } from "express";

describe("routes/emoji", () => {
  describe("/get/:emoji", () => {
    it("should fetch and return emoji passed as a param", async () => {
      // setup everything we need
      const computer = "mocked url";
      const data = { computer };
      const json = jest.fn().mockResolvedValue(data);
      const fetchMock = jest.fn(() => ({ json }));
      setFetch((fetchMock as unknown) as typeof fetch);

      const router = new EmojiRouter();

      // execute the code we want to test
      const request = ({ params: { emoji: "computer" } } as unknown) as Request;
      const response = ({} as unknown) as Response;
      response.json = jest.fn().mockReturnValue(response);
      await router.get(request, response);

      // assert the result
      expect(response.json).toHaveBeenCalledWith({ emoji: computer });
    });
    it("should return an error if not found", async () => {
      // setup everything we need
      const computer = "mocked url";
      const data = { computer };
      const json = jest.fn().mockResolvedValue(data);
      const fetchMock = jest.fn(() => ({ json }));
      setFetch((fetchMock as unknown) as typeof fetch);

      const router = new EmojiRouter();

      // execute the code we want to test
      const request = ({
        params: { emoji: "inexistent" }
      } as unknown) as Request;
      const response = ({} as unknown) as Response;
      response.status = jest.fn().mockReturnValue(response);
      response.json = jest.fn().mockReturnValue(response);
      await router.get(request, response);

      // assert the result
      expect(response.status).toHaveBeenCalledWith(404);
      expect(response.json).toHaveBeenCalledWith({ error: "emoji not found" });
    });
  });
});
```

{% endcode %}

{% hint style="info" %}
When types don't match, we use `as unknown` first to force TypeScript to accept the  other type. This is useful when we are mocking params.
{% endhint %}

We need a way to bootstrap our app (set containers, connect to the database, ...) and create an express app.

{% code title="app.ts" %}

```typescript
import * as express from "express";
import fetch from "node-fetch";
import { setFetch } from "./containers";
import { EmojiRouter } from "./routers";

export const bootstrap = async () => {
  setFetch(fetch);
  // connect to db here later...
};

export const createApp = () => {
  const app = express();
  app.use("/emoji", new EmojiRouter().router);
  return { app };
};
```

{% endcode %}

Our main file requires it and starts the server:

{% code title="index.ts" %}

```typescript
require("dotenv").config();

import { bootstrap, createApp } from "./app";
import { config } from "./config";

(async () => {
  await bootstrap();
  const { app } = createApp();
  app.listen(config.port, () => {
    console.info("Server listing on port " + config.port);
  });
})();
```

{% endcode %}

> Note: `sharedConfig` and `.env` needs to be updated to add `port`.

{% tabs %}
{% tab title="config/shared.ts" %}

```typescript
export interface SharedConfig {
  logLevel: string;
  port: number;
}

export const sharedConfig = {
  logLevel: process.env.LOG_LEVEL || "info", // we will come back to logs later
  port: parseInt(process.env.PORT || "1234")
};

```

{% endtab %}

{% tab title=".env" %}

```
CONFIG_ENV=local
LOG_LEVEL=debug
PORT=3000
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
[`express`](https://github.com/florianherrengt/book-code/tree/express) branch available on GitHub.
{% endhint %}


# Security

This is too often overlooked or completely ignored. But just a few lines of code can make your app much harder to break.

* [Helmet](https://helmetjs.github.io) helps you secure your Express apps by setting various HTTP headers.
* Add an API rate limit to reduce the number of actions and protect from DDoS and brute force attacks.
* Enforce HTTPS

### Helmet

```
$ yarn add helmet
```

Start protecting your app with `helmet`

```
import helmet from "helmet";

export const createApp = () => {
  // ...
  app.use(helmet());
  app.use(helmet.noCache()); // disable browser caching
  app.use(
    helmet.hsts({
      includeSubDomains: true, // enforce https everywhere
      preload: true
    })
  ); 
 // ...
};

```

### API Rate Limit

The library [node-rate-limiter-flexible](https://github.com/animir/node-rate-limiter-flexible#readme) helps us keep track of requests count.

```
$ yarn add rate-limiter-flexible
```

We are going to create a new folder `middlewares`and add `RateLimiter.ts` to it.

```
.
├── app.ts
└── middlewares
    ├── RateLimiter
    │   ├── index.spec.ts
    │   └── index.ts
    └── index.ts
```

{% tabs %}
{% tab title="middlewares/RateLimiter/index.ts" %}

```typescript
import { RateLimiterMemory, IRateLimiterOptions } from 'rate-limiter-flexible';
import { Handler } from 'express';
import { AppRequest } from '../app';

class RateLimiterMiddleware extends RateLimiterMemory {
  constructor(opts: IRateLimiterOptions = {}) {
    super({
      points: 10,
      duration: 1,
      ...opts,
    });
  }
  middleware: Handler = async (request: AppRequest, response, next) => {
    try {
      await this.consume(request.user ? request.user.emailAddress : request.ip);
      next();
    } catch (error) {
      response.sendStatus(429); // Too Many Requests
    }
  };
}

export { RateLimiterMiddleware };
```

{% endtab %}

{% tab title="middlewares/index.ts" %}

```typescript
export * from './RateLimiter'
```

{% endtab %}
{% endtabs %}

We can now use this middleware in `app.ts`.

{% code title="app.ts" %}

```typescript
import { RateLimiterMiddleware } from './middlewares'

export const createApp = () => {
  const app = express();
  app.use(new RateLimiterMiddleware().middleware);
};
```

{% endcode %}

The number of requests is now limited to 10 per seconds. Go ahead an experiment with higher numbers to see if you will get rejected.

Or instead, we can also write a test for this:

{% code title="\~/packages/api" %}

```
$ yarn add supertest @types/supertest -D
```

{% endcode %}

{% code title="middlewares/RateLimiter/index.spec.ts" %}

```typescript
import * as express from "express";
import * as supertest from "supertest";
import { RateLimiterMiddleware } from "./index";

describe("middlewares/RateLimiter", () => {
  it("should limit the amount of requests", async () => {
    const app = express();
    app.use(new RateLimiterMiddleware({ points: 1 }).middleware);
    app.get("/test", (_, response) => response.sendStatus(200));
    const agent = supertest(app);

    await agent.get("/test").expect(200);
    await agent.get("/test").expect(429);
  });
});
```

{% endcode %}

{% hint style="info" %}
[`security`](https://github.com/florianherrengt/book-code/tree/security) branch available on GitHub.
{% endhint %}


# Database

For this project, we will be using [Postgres](https://www.postgresql.org) as a database and the ORM [Sequelize](https://sequelize.org).\
They are amongst the most popular. But you can find [many other viable alternatives](https://github.com/numetriclabz/awesome-db).

### Run a local database

If you have installed docker, you can simply run the following command:

```
$ docker run -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=bookapp -p 5432:5432 postgres
```

Otherwise, you need to download and install it from the [official website](https://www.postgresql.org/download/).

Find a GUI to interact with your database. Have a look at [TablePlus](https://tableplus.com) (Free or $59) or [PgAdmin](https://www.pgadmin.org) (Open Source).

We should now be able to connect to our database:

```
Host: 127.0.0.1
Port: 5432
User: postgres
Password: postgres
Database: bookapp
```

Update the config and `.env`

{% tabs %}
{% tab title="config/shared.ts" %}

```typescript
export interface SharedConfig {
  logLevel: string;
  port: number;
  database: {
    dialect: "postgres";
    host: string;
    username: string;
    password: string;
    database: string;
    port: number;
    logging: boolean;
  };
}

export const sharedConfig: SharedConfig = {
  logLevel: process.env.LOG_LEVEL || "info", // we will come back to logs later
  port: parseInt(process.env.PORT || "1234"),
  database: {
    dialect: "postgres",
    host: process.env.PG_HOST || "",
    username: process.env.PG_USERNAME || "",
    password: process.env.PG_PASSWORD || "",
    database: process.env.PG_DATABASE || "",
    port: parseInt(process.env.PG_PORT || ""),
    logging: Boolean(parseInt(process.env.DB_LOGS || "0"))
  }
};

```

{% endtab %}

{% tab title=".env" %}

```
CONFIG_ENV=local
LOG_LEVEL=debug
PORT=3000
PG_HOST=localhost
PG_USERNAME=postgres
PG_PASSWORD=postgres
PG_DATABASE=bookapp
PG_PORT=5432
DB_LOGS=1
```

{% endtab %}
{% endtabs %}

### Models

Update our dependencies

```
$ yarn add sequelize sequelize-typescript pg
```

You'll see the following message:

```
warning " > sequelize-typescript@1.0.0" has unmet peer dependency "@types/bluebird@*".
warning " > sequelize-typescript@1.0.0" has unmet peer dependency "@types/node@*".
warning " > sequelize-typescript@1.0.0" has unmet peer dependency "@types/validator@*".
warning " > sequelize-typescript@1.0.0" has unmet peer dependency "reflect-metadata@*".
```

Fix it by installing the required dependencies:

```
$ yarn add -D @types/bluebird @types/node @types/validator
$ yarn add reflect-metadata
```

Let's create a folder `models` to keep our files organised and create a`Link` model.

```
.
├── app.ts
├── models
│   ├── Link.ts
│   └── index.ts
└── sequelize.ts
```

{% tabs %}
{% tab title="models/Link.ts" %}

```typescript
import { Model, Column, Table, DataType } from "sequelize-typescript";

@Table({
  tableName: "link",
  comment: "Links saved by a user"
})
class Link extends Model<Link> {
  @Column({
    primaryKey: true,
    allowNull: true,
    defaultValue: DataType.UUIDV4
  })
  id: string;

  @Column({
    allowNull: false
  })
  url: string;
}

export { Link };
```

{% endtab %}

{% tab title="models/index.ts" %}

```typescript
export * from "./Link";
```

{% endtab %}
{% endtabs %}

Create our Sequelize instance

{% code title="sequelize.ts" %}

```typescript
import { Sequelize } from "sequelize-typescript";
import { config } from "./config";
import * as models from "./models";

export const sequelize = new Sequelize({
  ...config.database,
  logging: config.database.logging ? console.log : false
});

sequelize.addModels(Object.values(models));
```

{% endcode %}

and sync our model when we bootstrap the app

{% code title="app.ts" %}

```typescript
import {sequelize} from './sequelize'

export const bootstrap = async () => {
  setFetch(fetch);
  await sequelize.sync()
};
```

{% endcode %}

```
$ ts-node src/index.ts
Executing (default): CREATE TABLE IF NOT EXISTS "link" ("id" VARCHAR(255) , "url" VARCHAR(255) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id")); COMMENT ON TABLE "link" IS 'Links saved by a user';
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'link' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Server listing on port 3000
```

We should now see the `link` table in our database (try to refresh if you don't).

![](/files/-LnhacEXU-EI4Tq7kuYz)

### Migrations

Let's try to rename `url` to `uri`in our `Link` model and run the app again.

```
$ ts-node src/index.ts
Executing (default): CREATE TABLE IF NOT EXISTS "link" ("id" VARCHAR(255) , "uri" VARCHAR(255) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id")); COMMENT ON TABLE "link" IS 'Links saved by a user';
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'link' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Server listing on port 3000
```

If we look in your UI, *nothing changed*.

This is because of `CREATE TABLE IF NOT EXISTS`. The table already exists and the statement gets ignored.

One way of solving this issue is to force sequelize to recreate all the tables with

```
sequelize.sync({ force: true });
```

{% hint style="danger" %}
Using`force: true`will remove the existing data. Use it only for development.
{% endhint %}

This is fine while we are building database and we don't have any data in production. However, how do we deal with this once we're live? We write migrations

#### Umzung

[Umzung](https://github.com/sequelize/umzug) is a framework agnostic migration tool for Node.js. We create migration files and umzug will run them one after the other. It will also store the last migration in a new table.

```
$ yarn add umzug @types/umzug
```

We will store our files in a new `migrations` folder and create our migration `001_rename_url_to_uri.ts`.

```
.
├── app.ts
├── migrate.ts
└── migrations
       └── 001_rename_url_to_uri.ts
```

{% code title="migrations/001\_rename\_url\_to\_uri.ts" %}

```typescript
import { QueryInterface } from 'sequelize';

module.exports = {
    // change the database schema
    async up(query: QueryInterface) {
        await query.renameColumn('link', 'url', 'uri');
    },

    // revert in case it goes wrong
    async down(query: QueryInterface) {
        await query.renameColumn('link', 'uri', 'url');
    },
};
```

{% endcode %}

We need a file to configure the migration

{% code title="migrate.ts" %}

```typescript
import * as path from "path";
import { Sequelize } from "sequelize";
import * as Umzug from "umzug";

const createUmzug = (sequelize: Sequelize) => {
  const umzug = new Umzug({
    storage: "sequelize",
    storageOptions: {
      sequelize
    },

    migrations: {
      params: [sequelize.getQueryInterface()],
      path: path.join(__dirname, "./migrations"),
      pattern: /\.ts$/
    }
  });

  function logUmzugEvent(
    eventName: "migrating" | "migrated" | "reverting" | "reverted"
  ) {
    return (name: string) => {
      console.log(`${name} ${eventName}`);
    };
  }

  umzug.on("migrating", logUmzugEvent("migrating"));
  umzug.on("migrated", logUmzugEvent("migrated"));
  umzug.on("reverting", logUmzugEvent("reverting"));
  umzug.on("reverted", logUmzugEvent("reverted"));

  return umzug;
};

export { createUmzug };
```

{% endcode %}

And run the migration when we start the app

{% code title="app.ts" %}

```typescript
export const bootstrap = async () => {
  setFetch(fetch);
  const umzug = createUmzug(sequelize);
  try {
    await umzug.up();
  } catch (e) {
    console.error("Migration failed.");
    await umzug.down();
    process.exit(1);
  }
  await sequelize.sync();
};
```

{% endcode %}

Run the app

```
$ ts-node src/index.ts
.
.
001_rename_url_to_uri migrated
.
.
Server listing on port 3000
```

Two things happened:

* `url` has been renamed to `uri`.
* A new table `SequelizeMeta` has been created.

![](/files/-LnhjP9WyBCjYcbSLWkg)

![SequelizeMeta will keep track of the latest migration.](/files/-LnhjfDRnHBJA7MePndi)

{% hint style="info" %}
[`database`](https://github.com/florianherrengt/book-code/tree/database) branch available on GitHub.
{% endhint %}


# GraphQL

There are many advantages to GraphQL but predictability is one of the best.

> Send a GraphQL query to your API and get exactly what you need, nothing more and nothing less. GraphQL queries always return predictable results.

[Apollo](https://apollographql.com) is one of the most popular libraries to build a GraphQL API today. It works well with Node and React. We will be using it. We will also use [TypeGraphQL](https://typegraphql.ml) to define our schema with TypeScript classes.

From now, we are building our app. So let's remove the migration until we need it. We'll keep one for reference.

{% hint style="info" %}
`sequelize.sync({ force: true })` will automatically create the required tables for you.
{% endhint %}

{% hint style="danger" %}
Don't forget to change it to `force: false` when you got live.
{% endhint %}

{% code title="001\_example.ts" %}

```typescript
import { QueryInterface } from "sequelize";

module.exports = {
  // change the database schema
  async up(query: QueryInterface) {
    // add migration here
  },

  // revert in case it goes wrong
  async down(query: QueryInterface) {
    // revert
  }
};
```

{% endcode %}

### Move from models to entities

The vast majority of GraphQL queries and mutations are to interact with the database.\
To avoid repeating ourselves, we will define your models and GraphQL schema with the same files.

Rename `models` to `entities` to reflect the semantic of our files.

{% code title="\~/packages/api/src" %}

```
$ mv models entities
```

{% endcode %}

Change `sequelize.ts` import path `import * as models from "./entities";`.

### Build the GraphQL Schema

Install dependencies

```
$ yarn add graphql @types/graphql type-graphql apollo-server-express
```

Following the [TypeGraphQL documentation](https://typegraphql.ml/docs/installation.html), we have to import `reflect-metadata` before we use/import `type-graphql` or our resolvers.

Update `Link.ts`to add `ObjectType` and `Field`.

{% code title="entities/Link.ts" %}

```typescript
import 'reflect-metadata';
import { Model, Column, Table, DataType } from "sequelize-typescript";
import { Field, ID, ObjectType } from 'type-graphql';

@ObjectType()
@Table({
  tableName: "link",
  comment: "Links saved by a user"
})
class Link extends Model<Link> {
  @Field(() => ID)
  @Column({
    primaryKey: true,
    allowNull: true,
    defaultValue: DataType.UUIDV4
  })
  id: string;

  @Field()
  @Column({
    allowNull: false
  })
  uri: string;
}

export { Link };
```

{% endcode %}

Create a resolver in a new folder `GraphQL` in`routers` as well as `resolvers` to keep things tidy.

```
.
├── GraphQL
    ├── index.ts
    └── resolvers
        ├── Link.ts
        └── index.ts
```

For now, we're just going to return a hardcoded array of Links

{% tabs %}
{% tab title="resolvers/Link.ts" %}

```typescript
import { Resolver, Query } from "type-graphql";
import { Link } from "../../../entities";

@Resolver(of => Link)
class LinkResolver {
  @Query(returns => [Link])
  links() {
    return [{ id: "123", uri: "http://test.com" }];
  }
}

export { LinkResolver };
```

{% endtab %}

{% tab title="resolvers.ts" %}

```typescript
export * from "./Link";
```

{% endtab %}
{% endtabs %}

Our new `GraphQL` router will build the schema

{% code title="routers/GraphQL/index.ts" %}

```typescript
import "reflect-metadata";
import { ApolloServer } from "apollo-server-express";

import { Router } from "express";

import { buildSchema } from "type-graphql";
import * as resolvers from "./resolvers";

export class GraphQLRouter {
  public readonly router = Router();
  constructor() {
    this.buildSchema();
  }
  async buildSchema() {
    const schema = await buildSchema({
      resolvers: Object.values(resolvers)
    });
    const apolloServer = new ApolloServer({
      schema
    });
    apolloServer.applyMiddleware({ app: this.router, path: "/" });
  }
}
```

{% endcode %}

Don't forget to add it to `app.ts`

{% code title="app.ts" %}

```typescript
export const createApp = () => {
  const app = express();
  // ...
  app.use("/graphql", new GraphQLRouter().router);
  return { app };
};
```

{% endcode %}

We are now able to send GraphQL queries. Let's open <http://localhost:3000/graphql> and see if it works.

![It works!](/files/-LnmzE-nJYOnMZyFFxwA)

### Return data from the database

Update the containers with `Link`

{% code title="container.ts" %}

```typescript
export const setLinkEntity = (entity: typeof Link) => Container.set(ContainerNames.linkEntity, entity)
export const getLinkEntity = (): typeof Link => Container.get(ContainerNames.linkEntity)
```

{% endcode %}

then set our `Link` entity when we declare it

{% code title="entities/Link.ts" %}

```typescript
// ...
import { setLinkEntity } from "../containers";

// ...
class Link extends Model<Link> {
 // ...
}

setLinkEntity(Link);

export { Link };
```

{% endcode %}

and use it in our resolver

### Pagination

Results should always be paginated for performance reasons. It's good practice to add it at the beginning.

We are going to create 2 helpers in `routers/GrapqhQL`.

The first one, `PaginatedResponse.ts`, generate a new class with to attributes:

* `items` the data returned. In this case, it will be `Link`.
* `total` the amount of items available to query.
* `hasMore` will be true if you can request more items.

{% code title="PaginatedResponse.ts" %}

```typescript
import { ClassType, ObjectType, Field, Int } from "type-graphql";

export function PaginatedResponse<TItem>(TItemClass: ClassType<TItem>) {
    // `isAbstract` decorator option is mandatory to prevent registering in schema
    @ObjectType({ isAbstract: true })
    abstract class PaginatedResponseClass {
        // here we use the runtime argument
        @Field(type => [TItemClass])
        // and here the generic type
        items: TItem[];

        @Field(type => Int)
        total: number;

        @Field(type => Boolean)
        hasMore: Boolean;
    }
    return PaginatedResponseClass;
}
```

{% endcode %}

The second one, `PaginatedArgs.ts`, adds query arguments:

* `limit` how many items to retrieve.
* `offset` how many items to skip.

{% code title="PaginatedArgs.ts" %}

```typescript
import { ArgsType, Field, Int } from "type-graphql";

@ArgsType()
class PaginatedArgs {
  @Field(type => Int, { nullable: true })
  offset?: number;

  @Field(type => Int, { nullable: true })
  limit?: number;
}

export { PaginatedArgs };
```

{% endcode %}

{% hint style="info" %}
We have to set `"declaration": false` in `tsconfig.json`.
{% endhint %}

Let's modify the `Link`resolver to use it:

{% code title="resolvers/Link.ts" %}

```typescript
import { Resolver, Query, ObjectType, Args } from "type-graphql";
import { Link } from "../../../entities";
import { getLinkEntity } from "../../../containers";
import { PaginatedResponse } from "../helpers";
import { PaginatedArgs } from "../helpers/PaginatedArgs";

@ObjectType()
class PaginatedLink extends PaginatedResponse(Link) { }

@Resolver(of => PaginatedLink)
class LinkResolver {
  private readonly _LinkEntity = getLinkEntity();
  @Query(returns => PaginatedLink)
  async links(@Args() { limit = 20, offset = 0 }: PaginatedArgs): Promise<PaginatedLink> {
    const { rows: items, count: total } = await this._LinkEntity.findAndCountAll({
      limit,
      offset,
      order: [['createdAt', 'DESC']]
    });
    return { items, total, hasMore: offset + items.length <= total };
  }
}

export { LinkResolver };
```

{% endcode %}

### Writing integration tests

We can use `createTestClient` from [ApolloTesting](https://www.apollographql.com/docs/apollo-server/testing/testing/).

Add the requires dependencies:

{% code title="\~/packages/api" %}

```
$ yarn add apollo-server-core apollo-server-testing
```

{% endcode %}

* Create a server
* Build the schema with the resolver
* Send the query

```typescript
import { ApolloServerBase, gql } from "apollo-server-core";
import {
  createTestClient,
  ApolloServerTestClient
} from "apollo-server-testing";
import { buildSchema } from "type-graphql";
import { LinkResolver } from "./Link";
import { setLinkEntity } from "../../../containers";
import { Link } from "../../../entities";

describe("routers/GraphQL/Resolvers/Link", () => {
  let testClient: ApolloServerTestClient;
  beforeAll(async () => {
    // create a graphql schema with the resolver
    const schema = await buildSchema({
      resolvers: [LinkResolver]
    });
    // create a server to send requests to
    const server = new ApolloServerBase({ schema });
    // use the test client to send queries and mutations
    // more info https://www.apollographql.com/docs/apollo-server/testing/testing
    testClient = createTestClient(server);
  });
  describe("query/links", () => {
    it("should return the list of links in the database", async () => {
      // mock sequelize
      const findAndCountAll = jest.fn();
      const fakeLink = { id: "1", uri: "http://test" };
      findAndCountAll.mockResolvedValue({ rows: [fakeLink], count: 1 });
      setLinkEntity(({ findAndCountAll } as unknown) as typeof Link);

      // send the query
      const { data, errors } = await testClient.query({
        query: gql`
          {
            links {
              items {
                id
                uri
              }
              total
            }
          }
        `
      });

      // assert results
      expect(errors).toBeUndefined();
      expect(data!.links.items).toHaveLength(1);
      expect(data!.links.items[0]).toMatchObject(fakeLink);
      expect(data!.links.hasMore).toBeFalsy();
    });
  });
});
```

### Conclusion

We have seen how to:

* Retrieve and create rows in the database
* Paginate the results
* Write isolated integration tests for the resolvers

The next step is now to add user authentication and make sure only owners can access their links.

{% hint style="info" %}
[`graphql`](https://github.com/florianherrengt/book-code/tree/graphql) branch available on GitHub.
{% endhint %}

{% code title="resolvers/Link.ts" %}

```typescript
import { Resolver, Query } from "type-graphql";
import { Link } from "../../../entities";
import { getLinkEntity } from "../../../containers";

@Resolver(of => Link)
class LinkResolver {
    private readonly _LinkEntity = getLinkEntity()
    @Query(returns => [Link])
    async links(): Promise<Link[]> {
        const links = this._LinkEntity.findAll()
        return links;
    }
}

export { LinkResolver };
```

{% endcode %}

{% hint style="info" %}
[`graphql`](https://github.com/florianherrengt/book-code/tree/graphql) branch available on GitHub.
{% endhint %}


# User authentication

Now that we can get the links saved, let's add a layer of security by using [jsonwebtoken](https://jwt.io/introduction/).

There are 2 important things you should always do:

* Hash password before storing them in the database. If you leak them, you won't expose the actual passwords.

![Source: http://www.commitstrip.com/en/2018/03/22/basic-functionality](/files/-LqHA4l-m1iGHTUTNHxS)

* Only store enough information in the JWT to identify the user. Anyone can read its content.

Try it yourself, take this token and paste it in [jwt.io](https://jwt.io).

```
eyJzZWNyZXQiOiIxMjMiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.OZ-CLQhIwvruoziWza1XEB4PwCNfMnlcljRt14_Zckw
```

![Anyone can read the content of a token](/files/-LqBwTmf73FJztKB0XvD)

{% hint style="danger" %}
Never store password in plain text.
{% endhint %}

{% hint style="danger" %}
Never put sensitive data in a JWT.
{% endhint %}

Install the required dependencies:

```
$ yarn add jsonwebtoken bcryptjs @types/jsonwebtoken @types/bcryptjs
```

We first going to create a GraphQL input to structure the data we receive:

{% code title="resolvers/UserAuthInput.ts" %}

```typescript
import { InputType, Field } from "type-graphql";

@InputType()
class UserAuthInput {
  @Field()
  emailAddress: string;

  @Field()
  password: string;
}

export { UserAuthInput };
```

{% endcode %}

Update the `User` entity to return the `jwt` with:

{% code title="entities/User.ts" %}

```typescript
class User extends Model<User> {
  // ...
  @Field({ nullable: true })
  jwt?: string;
}
```

{% endcode %}

Add your `jwtSecret` to the shared config:

```typescript
export interface SharedConfig {
  jwtSecret: string;
  // ...
}

export const sharedConfig: SharedConfig = {
  // ...
  jwtSecret: process.env.JWT_SECRET || "",
  // ...
};

```

And finally create the resolver:

```typescript
import { Resolver, Query, Mutation, Arg } from "type-graphql";
import * as bcrypt from "bcryptjs";
import { User } from "../../../../entities";
import { getUserEntity } from "../../../../containers";
import { UserAuthInput } from "./UserAuthInput";
import * as jwt from "jsonwebtoken";
import { config } from "../../../../config";

@Resolver(of => User)
class UserResolver {
  private readonly _UserEntity = getUserEntity();
  @Mutation(returns => User)
  async signIn(@Arg("input")
  {
    emailAddress,
    password
  }: UserAuthInput): Promise<User> {
    const user = await this._UserEntity.findOne({ where: { emailAddress } });
    if (!user) {
      throw new Error("User not found");
    }
    if (!bcrypt.compare(password, user.password)) {
      throw new Error("Invalid password");
    }
    user.jwt = jwt.sign({ id: user.id }, config.jwtSecret);
    return user;
  }
  @Mutation(returns => User)
  async signUp(@Arg("input")
  {
    emailAddress,
    password
  }: UserAuthInput): Promise<User> {
    if (await this._UserEntity.findOne({ where: { emailAddress } })) {
      throw new Error("User with this email address already exists.");
    }
    const salt = bcrypt.genSaltSync(10);
    try {
      const user = await this._UserEntity.create({
        emailAddress,
        password: bcrypt.hashSync(password, salt)
      });
      user.jwt = jwt.sign({ id: user.id }, config.jwtSecret);
      return user;
    } catch (error) {
      console.error("Cannot create user.", error);
      throw error;
    }
  }
}

export { UserResolver };
```

### Writing tests

Update `setupTests.ts` to require the environment variables:

{% code title="src/setupTests.ts" %}

```typescript
require('dotenv').config()
```

{% endcode %}

When building the schema, GraphQL will complain if we don't have any queries. For this reason, let's create a `Health` resolver and add it to our schema when testing.

{% code title="resolvers/Health.ts" %}

```typescript
import { Int, ObjectType, Query, Resolver, Field } from "type-graphql";

@ObjectType()
class Health {
  @Field(() => Int)
  ok: number;
}

@Resolver(of => Health)
class HealthResolver {
  @Query(returns => Health)
  async health(): Promise<Health> {
    return { ok: 1 };
  }
}

export { HealthResolver };
```

{% endcode %}

Finally, test the resolver:

```typescript
import { ApolloServerBase, gql } from "apollo-server-core";
import {
  createTestClient
} from "apollo-server-testing";
import { buildSchema } from "type-graphql";
import { UserResolver } from "./User";
import { setUserEntity } from "../../../../containers";
import { User } from "../../../../entities";
import { HealthResolver } from "..";
import * as jwt from 'jsonwebtoken'

describe("routers/GraphQL/Resolvers/User", () => {
  describe("mutation/signIn", () => {
    it("should return a jwt for this user", async () => {
      const schema = await buildSchema({
        resolvers: [HealthResolver, UserResolver]
      });
      const server = new ApolloServerBase({ schema });
      const testClient = createTestClient(server);
      const user = { emailAddress: "test1@mock.com", password: "123" };
      const findOne = jest.fn();
      findOne.mockResolvedValue({ ...user, id: 'userid', });
      setUserEntity(({ findOne } as unknown) as typeof User);

      const { data, errors } = await testClient.mutate({
        mutation: gql`
          mutation signIn($input: UserAuthInput!) {
            signIn(input: $input) {
              id
              emailAddress
              jwt
            }
          }
        `,
        variables: { input: user }
      });
      expect(errors).toBeUndefined();
      expect(data!.signIn).toMatchObject({ id: "userid", emailAddress: user.emailAddress })
      expect(jwt.decode(data!.signIn.jwt)).toMatchObject({ id: 'userid' })
    });
  });
  describe("mutation/signUp", () => {
    it("should create a user and return the jwt", async () => {
      const schema = await buildSchema({
        resolvers: [HealthResolver, UserResolver]
      });
      const server = new ApolloServerBase({ schema });
      const testClient = createTestClient(server);
      const user = { emailAddress: "test2@mock.com", password: "123" };
      const findOne = jest.fn();
      const create = jest.fn();
      create.mockResolvedValue({ id: "userid", ...user });
      findOne.mockResolvedValue(null);
      setUserEntity(({ findOne, create } as unknown) as typeof User);

      const { data, errors } = await testClient.mutate({
        mutation: gql`
          mutation signUp($input: UserAuthInput!) {
            signUp(input: $input) {
              id
              emailAddress
              jwt
            }
          }
        `,
        variables: { input: user }
      });
      expect(errors).toBeUndefined();
      expect(data!.signUp).toMatchObject({ id: "userid", emailAddress: user.emailAddress })
      expect(jwt.decode(data!.signUp.jwt)).toMatchObject({ id: 'userid' })
    });
  });
});
```

{% hint style="info" %}
Tests are running in parallel. We recreate the server for each test to avoid conflicts.
{% endhint %}

### Adding security to the links

How do we know which user just sent a request? GraphQL uses `context` to pass down information to all resolvers and we will use the `Authorization` header to send the JWT which each request.

### Populating the GraphQL context

{% code title="routers/GraphQL/index.ts" %}

```typescript
import "reflect-metadata";
import { ApolloServer } from "apollo-server-express";

import { Router } from "express";

import { buildSchema } from "type-graphql";
import * as resolvers from "./resolvers";

import * as jwt from "jsonwebtoken";
import { config } from "../../config";
import { User } from "../../entities";

export interface GraphQLContext {
  user?: User;
}

export class GraphQLRouter {
  public readonly router = Router();
  constructor() {
    this.buildSchema();
  }
  async buildSchema() {
    const schema = await buildSchema({
      resolvers: Object.values(resolvers),
      validate: false
    });
    const apolloServer = new ApolloServer({
      schema,
      context: ({ req }): GraphQLContext => {
        if (!req.headers.authorization) {
          return {};
        }
        if (req.headers.authorization.slice(0, 7).trim() !== "Bearer") {
          return {};
        }
        try {
          return {
            user: jwt.verify(
              req.headers.authorization.slice(7),
              config.jwtSecret
            ) as User
          };
        } catch (error) {
          return {};
        }
      }
    });
    apolloServer.applyMiddleware({ app: this.router, path: "/" });
  }
}
```

{% endcode %}

### Testing

We can mock the context when creating the test server:

```typescript
const server = new ApolloServerBase({
  schema,
  context: { user: { id: "userid" } }
});
```

### Getting the context from resolvers

Add the argument `@Ctx() context` to a resolver:

```typescript
@Mutation(returns => Link)
  async addLink(
    @Arg("input") input: LinkInput,
    @Ctx() context: GraphQLContext
  ): Promise<Link> {
    if (!context.user) {
      throw new Error("Unauthorised");
    }
    const link = await this._LinkEntity.create({
      ...input,
      userId: context.user.id
    });
    return link;
  }
```

### Adding headers in the playground

Use the tab `HTTP HEADERS` at the bottom left.

![](/files/-LqCKKanD3SmjCLwwEtE)

### Conclusion

The backend is now ready to interact with the frontend. We can interact safely with the database and we have enough to implement anything we might need.

A lot is going on in this chapter. Have look at this [Pull Request](https://github.com/florianherrengt/book-code/pull/2) to see all the changes with comments.

{% hint style="info" %}
[user-auth](https://github.com/florianherrengt/book-code/tree/user-auth) branch available on GitHub.
{% endhint %}


# Conclusion

This is now the end of this chapter.&#x20;

We covered the following concepts:

* Organising your files in a scalable way.
* Reading secrets from environment variables.
* Managing different configurations based on your environments.
* How to use Express and GraphQL together.
* How to build a strongly typed GraphQL API.
* How to add an extra layer on security.
* Connecting to the database.
* Mocking database interaction and the GraphQL server.
* User authentication.

If you have any questions, feel to reach me on Twitter [@florianherrengt](https://twitter.com/florianherrengt).

Want to hire us? [Let's chat.](https://calendly.com/florianherrengt/15min)&#x20;


# Frontend


# Create React App

Let's get started by using `create-react-app`. It will bootstrap the project with everything needed.

```
$ cd packages
$ rm -rf web
$ npx create-react-app web --typescript
```

Delete unnecessary files like `.gitignore` and `README.md`.


# Files organisation

There are 2 important concepts with our React app: `pages` and`components`.

### Pages

Each folder correspond to a URL and contains everything the page needs.

Example `/links` displays all the links saved by a user.

```
.
└── src
    └── pages
        └── links
            ├── Link.tsx
            ├── LinkList.tsx
            └── index.tsx
```

`Link.tsx`is responsible for display the link's information:

{% code title="Link.ts" %}

```typescript
import * as React from "react";

export interface LinkProps {
  id: string;
  uri: string;
  userId: string;
}

export const Link = (props: LinkProps) => <div>URI: {props.uri}</div>;
```

{% endcode %}

and `LinkList.tsx` display them as a list:

{% code title="LinkList.tsx" %}

```typescript
import * as React from "react";
import { LinkProps, Link } from "./Link";

export interface LinkListProps {
  links: LinkProps[];
}

export const LinkList = (props: LinkL
istProps) => (
  <div>
    {props.links.map(link => (
      <Link {...link} />
    ))}
  </div>
);
```

{% endcode %}

Then `LinksPage.ts` fetches the data and uses the created components to display it:

{% tabs %}
{% tab title="LinksPage.tsx" %}

```typescript
import * as React from "react";
import { LinkList } from "./LinkList";

const getData = () => [{ id: "1", uri: "http://mock", userId: "userid" }];

export const LinksPage = () => (
  <div>
    <LinkList links={getData()} />
  </div>
);

// we export default pages for code splitting later
// more info at https://reactjs.org/docs/code-splitting.html
export default LinksPage;
```

{% endtab %}

{% tab title="index.tsx" %}

```typescript
export * from "./LinksPage";
```

{% endtab %}
{% endtabs %}

`Link` and `LinkList` are `components` but to keep it simple, we keep things related to each other close to each other. This prevent *files juggling* when editing a page.

If we use `Link` in more than one page, then we will move it to the`components` folder.

### Components

This folder containers all components that are used in more that one place. Let's add a `Header` to our `LinksPage`.

```
.
├── src
    ├── components
    │   └── Header
    │       ├── Header.tsx
    │       └── index.tsx
    └── pages
        └── links
            ├── Link.tsx
            ├── LinkList.tsx
            └── index.tsx
```

{% code title="Header.tsx" %}

```typescript
import * as React from "react";

export const Header = () => <div>Header here</div>;
```

{% endcode %}

{% code title="LinksPage.tsx" %}

```typescript
import * as React from "react";
import { LinkList } from "./LinkList";
import { Header } from "../../components/Header";

const getData = () => [{ id: "1", uri: "http://mock", userId: "userid" }];

export const LinksPage = () => (
  <div>
    <Header />
    <LinkList links={getData()} />
  </div>
);

// we export default pages for code splitting later
// more info at https://reactjs.org/docs/code-splitting.html
export default LinksPage;
```

{% endcode %}

Replace everything in `App.tsx`

{% code title="App.tsx" %}

```typescript
import React from 'react';
import './App.css';
import { LinksPage } from './pages/links';

const App: React.FC = () => {
  return (
    <div className="App">
      <LinksPage />
    </div>
  );
}

export default App;
```

{% endcode %}

Run `yarn start` and open [localhost:3000](http://localhost:3000/).

![](/files/-LqHyhWH_5n8p26jq7YT)


# Styles

So far, components are rather unattractive. Let's start using [`styled-components`](https://www.styled-components.com).

{% code title="\~/packages/web" %}

```
$ yarn add styled-components @types/styled-components
```

{% endcode %}

{% code title="Link.tsx" %}

```typescript
import * as React from "react";
import styled from "styled-components";

export interface LinkProps {
  id: string;
  uri: string;
  userId: string;
}

const Container = styled.div`
  border: 1px solid gray;
  width: 200px;
  padding: 20px;
`;

export const Link = (props: LinkProps) => (
  <Container>URI: {props.uri}</Container>
);
```

{% endcode %}

![](/files/-LqI0wl-usZg-ZEueL64)

{% hint style="info" %}
Read the [Styled Component documentation](https://www.styled-components.com/docs/).
{% endhint %}


# Apollo Hooks

It is now time to get some real data from the server. Remember `Health`? Let's display the result on a page using [Apollo Hooks](https://www.apollographql.com/docs/tutorial/queries/#the-usequery-hook).

{% code title="\~/packages/web" %}

```
$ yarn add apollo-boost react-apollo apollo-client graphql apollo-utilities apollo-cache apollo-link
```

{% endcode %}

Update `App.tsx` to use `ApolloProvider`:

```typescript
import React from "react";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import "./App.css";
import { LinksPage } from "./pages/links";

const client = new ApolloClient({ uri: "/graphql" });

const App: React.FC = () => {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <LinksPage />
      </div>
    </ApolloProvider>
  );
};

export default App;
```

To tell the development server to proxy any unknown requests to your API server in development, add a `proxy` field to your `package.json:`

```
"proxy": "http://localhost:8080"
```

{% hint style="info" %}
Make sure the server is running.&#x20;
{% endhint %}

Update `LinksPages` to query `Health`:

```typescript
import * as React from "react";
import { LinkList } from "./LinkList";
import { Header } from "../../components/Header";
import { useQuery } from "react-apollo";
import { gql } from "apollo-boost";

const getData = () => [{ id: "1", uri: "http://mock", userId: "userid" }];

export const LinksPage = () => {
  const { data, loading, error } = useQuery(gql`
    {
      health {
        ok
      }
    }
  `);
  if (loading) {
    return <div>Loading...</div>;
  }
  if (error) {
    return <div>Oops... Something wrong happened. Try again later.</div>;
  }
  return (
    <div>
      <Header />
      <div>Health {data.health.ok}</div>
      <LinkList links={getData()} />
    </div>
  );
};

export default LinksPage;
```

We can send request to our GrapqhQL API. Now we need to add user authentication.

{% hint style="info" %}
[`apollo-hooks`](https://github.com/florianherrengt/book-code/tree/apollo-hooks) branch available on GitHub.
{% endhint %}


# Form management

Before we can identify the user when we receive a request, we need to create a form to collect credentials.

![](/files/-Lqg20Eoicu8tRJ7c3Qv)

We will use [Formik](https://jaredpalmer.com/formik/) to build our forms.

```
$ yarn add @types/react formik
```

We need 2 components to build our form, `Input` and `Button`which we can then use to build our form. We send the request to the server using [`useMutation`](https://www.apollographql.com/docs/tutorial/mutations/#update-data-with-usemutation).

{% tabs %}
{% tab title="components/Input.ts" %}

```typescript
import * as React from "react";
import { FieldProps } from "formik";
import styled from "styled-components";
import { theme } from "../../config";

const Container = styled.div``;
const StyledInput = styled.input`
  border: 1px solid ${theme.colors.border};
  border-radius: 4px;
  padding: 8px;
  font-size: 14px;
  width: 100%;
  box-sizing: border-box;

  &:focus {
    border-color: ${theme.colors.background2};
    outline: none;
  }
`;
const Error = styled.div`
  color: red;
`;

export const Input: React.SFC<FieldProps<any>> = ({
  field, // { name, value, onChange, onBlur }
  form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
  ...props
}) => (
  <Container>
    <StyledInput type="text" {...field} {...props} />
    {touched[field.name] && errors[field.name] && (
      <Error>{errors[field.name]}</Error>
    )}
  </Container>
);
```

{% endtab %}

{% tab title="components/Button.tsx" %}

```typescript
import * as React from "react";
import styled from "styled-components";
import { theme } from "../../config";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons";
import { ButtonHTMLAttributes } from "react";

export interface ButtonProps {
  isLoading?: boolean;
  color: keyof typeof theme.colors;
  onClick?: () => void;
}

const StyledButton = styled.button<ButtonProps>`
  border: 1px solid ${props => theme.colors[props.color]};
  background-color: ${props => theme.colors[props.color]};
  color: ${theme.colors.contrast2};
  border-radius: 4px;
  padding: 8px 20px;
  font-size: 12px;
  cursor: pointer;
  width: 100%;

  &:disabled {
    opacity: 0.5;
  }
`;

const StyledIcon = styled(FontAwesomeIcon)`
  margin: 0 !important;
`;

export const Button: React.SFC<
  ButtonProps & ButtonHTMLAttributes<HTMLButtonElement>
> = props => {
  return (
    <StyledButton {...props}>
      {props.isLoading ? (
        <StyledIcon spin icon={faCircleNotch} />
      ) : (
        props.children
      )}
    </StyledButton>
  );
};
```

{% endtab %}

{% tab title="pages/SignupPage.tsx" %}

```typescript
import * as React from "react";
import { useMutation } from "react-apollo";
import { gql } from "apollo-boost";
import { Formik, Field, Form, FormikActions } from "formik";
import styled from "styled-components";
import { Input } from "../../components/Input";
import { Button } from "../../components/Button";
import { theme } from "../../config";
import { useHistory } from "react-router";

interface FormValues {
  emailAddress: string;
  password: string;
}

const Container = styled.div`
  max-width: 400px;
  margin-right: auto;
  margin-left: auto;
  box-shadow: 0px 0px 10px lightgrey;
  padding: 30px 50px 50px;
  margin-top: 10vh;
`;

const Title = styled.h1`
  margin: 20px 0;
  font-size: 20px;
  font-weight: bold;
`;

const StyledForm = styled(Form)`
  input {
    margin-bottom: 20px;
  }
  button {
    margin-top: 20px;
  }
`;

const StyledErrorMessage = styled.div`
  color: ${theme.colors.negative};
`;

export const SignupPage = () => {
  const history = useHistory();
  const [signUpMutation, { loading, error }] = useMutation(
    gql`
      mutation signUp($input: UserAuthInput!) {
        signUp(input: $input) {
          id
          emailAddress
          jwt
        }
      }
    `,
    {
      onCompleted({ signUp }) {
        localStorage.setItem("token", signUp.jwt);
        history.push("/");
      }
    }
  );

  return (
    <Container>
      <Title>Create an account</Title>
      <Formik
        initialValues={{
          emailAddress: "",
          password: ""
        }}
        onSubmit={async (
          values: FormValues,
          { setSubmitting }: FormikActions<FormValues>
        ) => {
          signUpMutation({ variables: { input: values } });
          setSubmitting(false);
        }}
        render={() => (
          <StyledForm>
            <Field
              component={Input}
              id="emailAddress"
              name="emailAddress"
              placeholder="Email address"
              type="email"
            />

            <Field
              component={Input}
              id="password"
              name="password"
              placeholder="Password"
              type="password"
              autoComplete="new-password"
            />
            {error && (
              <StyledErrorMessage>
                {error.graphQLErrors[0].message}
              </StyledErrorMessage>
            )}

            <Button isLoading={loading} color="positive" type="submit">
              Submit
            </Button>
          </StyledForm>
        )}
      />
    </Container>
  );
};
```

{% endtab %}
{% endtabs %}

We also use [React Router](https://reacttraining.com/react-router/) and [Lazy](https://reactjs.org/docs/code-splitting.html):

{% code title="App.tsx" %}

```typescript
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import reset from "styled-reset";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import { LoadingScreen } from "./components/Loading";
import { Header } from "./components/Header";
import { createGlobalStyle } from "styled-components";

const SignupPage = lazy(() => import("./pages/signup"));
const LinksPage = lazy(() => import("./pages/links"));

const client = new ApolloClient({ uri: "/graphql" });

const GlobalStyle = createGlobalStyle`
  ${reset}
  body {
    font-family: "Open Sans", sans-serif;
  }
  a {
    text-decoration: none;
    color: inherit;
  }
`;

const App: React.FC = () => {
  return (
    <ApolloProvider client={client}>
      <GlobalStyle />
      <div className="App">
        <Router>
          <Header />
          <Switch>
            <Route path="/signup">
              <Suspense fallback={<LoadingScreen />}>
                <SignupPage />
              </Suspense>
            </Route>
            <Route path="/users">
              <Suspense fallback={<LoadingScreen />}>
                <LinksPage />
              </Suspense>
            </Route>
            <Route path="/">
              <div>Home</div>
            </Route>
          </Switch>
        </Router>
      </div>
    </ApolloProvider>
  );
};

export default App;
```

{% endcode %}

This cover everything needed to interact with the GraphQL API and display the results.

{% hint style="info" %}
[`form`](https://github.com/florianherrengt/book-code/tree/form) branch and [pull request](https://github.com/florianherrengt/book-code/pull/3) with comments available on GitHub.
{% endhint %}


# User authentication

Now that users can sign up, let's see how do send the token with all requests.

We need to remove `apollo-boost` to start using `apollo-link`.

{% code title="\~/packages/web" %}

```
$ yarn remove apollo-boost
$ yarn add apollo-link-http apollo-link-context apollo-cache-inmemory graphql-tag
```

{% endcode %}

In `App.tsx` replace:

```typescript
import ApolloClient from "apollo-boost";
const client = new ApolloClient({ uri: "/graphql" });
```

with:

```typescript
import ApolloClient from "apollo-client";
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';

const httpLink = createHttpLink({
  uri: '/graphql',
});

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('token');
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});
```

Also replace `import { gql } from "apollo-boost"` with`import gql from "graphql-tag";`

To see if this is working we can log out `context` in our `Health` resolver:

{% code title="resolvers/Health.ts" %}

```typescript
@Resolver(of => Health)
class HealthResolver {
  @Query(returns => Health)
  async health(@Ctx() context: GraphQLContext): Promise<Health> {
    console.log({ context })
    return { ok: 1 };
  }
}
```

{% endcode %}

`LinksPage.tsx` is still sending a query to `Health.` You should see the following in your terminal:

```
{
  context: {
    user: { id: '9cd7e02b-0333-4d67-a43b-6957fb38caac', iat: 1570621069 }
  }
}
```

{% hint style="info" %}
[`send-token`](https://github.com/florianherrengt/book-code/tree/send-token)branch available on GitHub.
{% endhint %}


# Writing tests

This is what we want to test in `SignupPage.tsx`:

* The form is filled up correctly.
* The values submitted come from the form.
* The token is added to the local storage.
* The user is redirected to the home page.

To achieve this, we will need to:

* Simulate keyboard and mouse events.
* Mock Apollo provider.
* Mock `localStorage`.
* Mock `history`.

### Simulate keyboard and mouse events

There are two ways to do this: `Simulate` from `react-dom/test-utils` or using `dispatchEvent`:

```typescript
import { act, Simulate } from "react-dom/test-utils";

const emailInput = container.querySelector("input[name=emailAddress]")!;
emailInput.setAttribute("value", "test@example.com");
Simulate.change(emailInput);

const submitButton = container.querySelector("button[type=submit]")!;
expect(submitButton.textContent).toEqual("Submit");
submitButton.dispatchEvent(new MouseEvent("click"));
```

{% hint style="info" %}
Learn more about rendering components with `test-utils` from the [Testing Recipes](https://reactjs.org/docs/testing-recipes.html).
{% endhint %}

### Mock Apollo provider

We need to export the mutation from the page to be able to use it in our test:

{% code title="pages/SignupPage.tsx" %}

```typescript
export const SIGNUP_MUTATION = gql`
  mutation signUp($input: UserAuthInput!) {
    signUp(input: $input) {
      id
      emailAddress
      jwt
    }
  }
`;

export const SignupPage = () => { ... }
```

{% endcode %}

We can now use `MockedProvider` from[`@apollo/react-testing`](https://www.apollographql.com/docs/react/api/react-testing/).

```typescript
const mocks = [
    {
      request: {
        query: SIGNUP_MUTATION,
        variables: {
          input: {
            emailAddress: "test@example.com",
            password: "qwerty123"
          }
        }
      },
      result: {
        data: {
          signUp: {
            id: "userid",
            emailAddress: "test@example.com",
            jwt: "faketoken"
          }
        }
      }
    }
  ];
  
await act(async () => {
   render(
     <MockedProvider mocks={mocks} addTypename={false}>
       <SignupPage />
     </MockedProvider />
    );
});
```

{% hint style="info" %}
Learn more about `MockedProvider`with [Testing React components](https://www.apollographql.com/docs/react/development-testing/testing/).
{% endhint %}

### Mock local storage

Jest comes with ways to easily mock functions:

```typescript
const spy = jest.spyOn(Storage.prototype, "setItem");
// ... expect(window.localStorage.setItem).to..;
spy.mockRestore();
```

The important part is to know how to mock things. (e.g `Storage.prototype` for `localStorage`).

### Mock history

We can use `createMemoryHistory` from[`history`](https://github.com/ReactTraining/history).

```typescript
const history = createMemoryHistory({ initialEntries: ["/signup"] });
```

```typescript
<Router history={history}>
  <Route path="/signup" component={SignupPage} />
</Router>
```

### Bring it together

{% code title="pages/SignupPage.spec.tsx" %}

```typescript
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act, Simulate } from "react-dom/test-utils";
import { SignupPage, SIGNUP_MUTATION } from "./SignupPage";
import { createMemoryHistory } from "history";
import { Router, Route } from "react-router";
import { MockedProvider, wait } from "@apollo/react-testing";
import { ThemeProvider } from "styled-components";

let container: HTMLDivElement;
beforeEach(() => {
  // setup a DOM element as a render target
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  // cleanup on exiting
  unmountComponentAtNode(container);
  container.remove();
  container = (null as unknown) as HTMLDivElement; // prevent memory leaks
});

it("renders user data", async () => {
  // setup the test
  const history = createMemoryHistory({ initialEntries: ["/signup"] });
  let signUpMutationCalled = false;
  const spy = jest.spyOn(Storage.prototype, "setItem");

  const mocks = [
    {
      request: {
        query: SIGNUP_MUTATION,
        variables: {
          input: {
            emailAddress: "test@example.com",
            password: "qwerty123"
          }
        }
      },
      result: () => {
        signUpMutationCalled = true;
        return {
          data: {
            signUp: {
              id: "userid",
              emailAddress: "test@example.com",
              jwt: "faketoken"
            }
          }
        };
      }
    }
  ];

  await act(async () => {
    render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <ThemeProvider theme={{}}>
          <Router history={history}>
            <Route path="/signup" component={SignupPage} />
          </Router>
        </ThemeProvider>
      </MockedProvider>,
      container
    );

    // fill up the form
    const emailInput = container.querySelector("input[name=emailAddress]")!;
    emailInput.setAttribute("value", "test@example.com");
    Simulate.change(emailInput);

    const passwordInput = container.querySelector("input[name=password]")!;
    passwordInput.setAttribute("value", "qwerty123");
    Simulate.change(passwordInput);
  });

  await act(async () => {
    // submit the form
    // triggering the hook has to be in another `act`
    const submitButton = container.querySelector("button[type=submit]")!;
    expect(submitButton.textContent).toEqual("Submit");
    submitButton.dispatchEvent(new MouseEvent("click"));
    await wait(0);
    // check loading state
    expect(submitButton.textContent).not.toEqual("Submit");
  });

  // check if the mutation has been  called
  expect(signUpMutationCalled).toBeTruthy();

  // check if the token has been set correctly
  expect(window.localStorage.setItem).toHaveBeenCalledWith(
    "token",
    "faketoken"
  );
  spy.mockRestore();

  // check if the user has been redirected
  expect(history.location.pathname).toBe("/");
});
```

{% endcode %}

{% hint style="info" %}
[`react-unit-test`](https://github.com/florianherrengt/book-code/tree/react-unit-test) branch and [pull request](https://github.com/florianherrengt/book-code/tree/react-unit-test) available on GitHub.
{% endhint %}


# Types generation

Types are only enforced between the backend and frontend with GraphQL at runtime (when sending the request). What happens if we change something in the backend and forget to reflect this change in the frontend? It will crash. Let's make sure this doesn't happen.

We are going to generate types from the GraphQL schema with [Apollo CLI](https://github.com/apollographql/apollo-tooling). Then use them on the frontend. If something changes on the backend, Typescript will let us know.

{% code title="\~/" %}

```
yarn add apollo
```

{% endcode %}

Start the server:

{% code title="\~/packages/api" %}

```
$ ts-node -T src/index.ts
```

{% endcode %}

Create a new package:

{% code title="\~/" %}

```
mkdir packages/types
```

{% endcode %}

Generate the types:

{% code title="\~/packages" %}

```
apollo codegen:generate --target=typescript --queries='packages/web/src/**/*.tsx' --endpoint=http://localhost:8080/graphql --tagName=gql --outputFlat packages/types/graphql.d.ts
```

{% endcode %}

To fix `Apollo does not support anonymous operations` change:

{% code title="LinksPage.tsx" %}

```typescript
const { data, loading, error } = useQuery(gql`
    {
      health {
        ok
      }
    }
  `);
```

{% endcode %}

to

{% code title="LinksPage.tsx" %}

```typescript
const { data, loading, error } = useQuery(gql`
    query health {
      health {
        ok
      }
    }
  `);
```

{% endcode %}

You should now see `types.d.ts` in `packages/types`. Let's create a `package.json` and `index.d.ts` for this package:

{% tabs %}
{% tab title="\~/packages/types/package.json" %}

```typescript
{
  "name": "api-types",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {}
}

```

{% endtab %}

{% tab title="index.d.ts" %}

```typescript
export * from './graphql'
```

{% endtab %}
{% endtabs %}

and now use Lerna to add the `types` to `web`:

```
$ lerna add api-types --scope=web
```

We can now import the variables:

```typescript
import {
  signUp as SignUpResponse,
  signUpVariables as SignUpVariables
} from "api-types";
```

and enforce types with queries and mutations:

```typescript
const [signUpMutation, { loading, error }] = useMutation<
    SignUpResponse,
    SignUpVariables
  >(SIGNUP_MUTATION, {
    onCompleted({ signUp }) {
      // ...
    }
  });
```

{% hint style="warning" %}
`yarn add <dependency>` won't work in anymore. Use `lerna add dependency> --scope=web` instead.
{% endhint %}

Later, if we have a React Native app or a queue worker, we will be able to reuse the types from this package.

{% hint style="info" %}
[`types-generation`](https://github.com/florianherrengt/book-code/tree/types-generation) branch and [pull request](https://github.com/florianherrengt/book-code/pull/5/files) available on GitHub.
{% endhint %}


# Conclusion

Let's review what we covered:

* Organise files between pages and components while keeping related files close to each other.
* How to use Styled Component
* Send queries and mutations to the GraphQL API and display the results
* Manage form inputs
* Write tests
* Generate types to keep backend and frontend in sync.


# DevOps


# CI/CD

The first thing we're going to do is automating the tests. The CI will run the `backend` and `frontend` tests for each commit pushed.

Later, we will also automate the deployment of the app when we push to the `master` branch.

The end goal is to automate the pipeline from commit to deploy.

![](/files/-LrEq6kTHyZv2559o8iH)

### CircleCI

[CircleCI](https://circleci.com) is a popular platform offering CI in the cloud. You get one container for free.\
Let's get started:

1. Create a folder named `.circleci` and add a file`config.yml`.
2. Populate the `config.yml` with the contents of the sample `.yml` bellow.
3. Change `"test": "echo \"Error: no test specified\" && exit 1"` to `"test": "jest"` in `packages/api`.
4. Commit and push up to GitHub
5. Go to CircleCI and watch your build.

```yaml
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/node:10

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/mongo:3.4.4

    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "packages/api/package.json" }}-{{ checksum "packages/web/package.json" }}
            - v1-dependencies-

      - run: yarn install

      - save_cache:
          paths:
            - node_modules
            - packages/web/node_modules
            - packages/api/node_modules
          key: v1-dependencies-{{ checksum "package.json" }}-{{ checksum "packages/api/package.json" }}-{{ checksum "packages/web/package.json" }}

      # run tests!
      - run: yarn --cwd packages/web test
      - run: yarn --cwd packages/api test
```

When tests are broken, CircleCI will let you know:

![](/files/-LrEviS5SIjsx70QbA0g)

We can also see the build status on the commits:

![The  ❌ on the right end side indicates the build is not passing.](/files/-LrEw2uSXQV4iXLTKoA3)

{% hint style="info" %}
You can configure GitHub to require the CI to pass before you merge into a branch in `Setting`> `Branches` > `Rule settings` > `Require status checks to pass before merging`.

* Branch name pattern: `master`
* Check CircleCI steps
  {% endhint %}

### Environment variables

Locally, we are reading the `.env` file. But this file is not committed to the repo. We have to setup the environment  variables on CircleCI.

Go to the project's settings by clicking on the cog next to the project name:

![](/files/-LrEybPgtNOfOqTQ7eBu)

Scroll down to `BUILD SETTINGS` and click `Environment Variables` then `Add Variable`.\
Add the variables from the `.env` file:

![](/files/-LrEz5lmiCjPFkIooNnZ)

The tests should now be passing:

![](/files/-LrEzyhCEQFSdJT1_cGC)

![](/files/-LrF0E7xIGHXNJO0KIA8)


# AWS

### Pricing

Predicting the bill is sometimes complicated on AWS. Be careful with what resources you are spinning up.

![The cheapest production-ready Postgres on RDS.](/files/-LrF2J7Dea4zWQXmUw2g)

In this tutorial, we assume the app will run in production and use the best resources available to make our app scalable and reliable.

### How much will it cost?

This is the break down of the monthly cost of the resources we will use:

* **Route 53:**
  * $0.40 per 1,000,000 queries for the first 1 Billion queries
  * $0.50 per Hosted Zone for the first 25 Hosted Zones
* **NAT Gateway**: $35 + $0.048 per GB Data Processed
* **Aurora Serverless**: $45


# Managing secrets

Go `AWS Systems Manager` and scroll down to `Shared Resources` > `Parameter Store` then click `Create parameter`.

![](/files/-LrJ38ydGken6tUiyn3V)

You need to pick a consistent format for your parameter names. I recommend using: `/<app_name>/<stage>/<variable_name>`.

![](/files/-LrJ49ItW0garieeuI-N)

All secret can be safely stored here.


# Pricing

Predicting the bill is sometimes complicated on AWS. Be careful with what resources you are spinning up.

![The cheapest production-ready Postgres on RDS.](/files/-LrF2J7Dea4zWQXmUw2g)

In this tutorial, we assume the app will run in production and use the best resources available to make our app scalable and reliable.

### How much will it cost?

This is the break down of the cost of the resources we will use:

* **Route 53:**
  * $12 per year for the domain
  * $0.40 per 1,000,000 queries for the first 1 Billion queries
  * $0.50 per month per Hosted Zone for the first 25 Hosted Zones
* **NAT Gateway**: \~$35 per month + $0.048 per GB Data Processed
* **Aurora Serverless**: \~$45 per month

The other resource should fall under the "Free tier".

{% hint style="info" %}
If you're running a low traffic/non-critical app, have a look at [Amazon Lightsail](https://aws.amazon.com/lightsail/pricing/). You can get a small instance for $3.50/m and managed database for $15. As your needs expand, you can easily migrate to EC2 later.

There are also alternatives where you can get 2 load balanced instances and a Postgres database. Have a look at [Heroku](https://www.heroku.com/pricing) ($100) or [DigitalOcean](https://www.digitalocean.com/pricing/) ($40).
{% endhint %}


# RDS

We are going to use [Aurora Serverless](https://aws.amazon.com/rds/aurora/serverless/). We will pay only for the resources we use and it will scale up or down automatically.

Search for `RDS` in `Services`:

![](/files/-LrFQSG3FyLjHdUTopZL)

Click `Databases` and `Create database`:

![](/files/-LrFQq1LssU5CMYZi4xG)

Scroll down and you find the "Serverless" option:

![](/files/-LrFR-xsjV0FiNQlwl0K)

There are a few important limitations to the serverless version of Aurora:

* You can't give an Aurora Serverless DB cluster a public IP address. This means you can't access it from TablePlus (or similar) from your computer.
* The resources have to be in the same VPC to access the database (this is why we need the NAT).
* You can't turn on [Amazon RDS Performance Insights](https://docs.aws.amazon.com/en_pv/AmazonRDS/latest/AuroraUserGuide/USER_PerfInsights).
* It is compatible only with PostgreSQL version `10.7`. We need to reflect this to our local setup for development. `10.7` is not available on Docker Hub but `10` should work fine.\
  `docker run -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=bookapp -d -p 5432:5432 postgres:10`


# S3

Static files and assets will be stored on S3.

The first thing we have to do is to build the React app.

```
$ cd packages/web
$ yarn build
Creating an optimized production build...
Compiled successfully.
...
✨  Done in 20.37s.
```

We now have a `build` folder:

```
build
├── asset-manifest.json
├── favicon.ico
├── index.html
├── manifest.json
├── precache-manifest.cdaef9d20bdd2939b5087486a61c7d6a.js
├── robots.txt
├── service-worker.js
└── static
    └── js
        ├── 0.b9fba7db.chunk.js
        ├── 0.b9fba7db.chunk.js.map
        ├── 3.b4b93fe8.chunk.js
        ├── 3.b4b93fe8.chunk.js.map
        ├── 4.d21e26c5.chunk.js
        ├── 4.d21e26c5.chunk.js.map
        ├── 5.bc17229d.chunk.js
        ├── 5.bc17229d.chunk.js.map
        ├── 6.0d642d4a.chunk.js
        ├── 6.0d642d4a.chunk.js.map
        ├── main.cb6ee362.chunk.js
        ├── main.cb6ee362.chunk.js.map
        ├── runtime-main.416359ae.js
        └── runtime-main.416359ae.js.map
```

{% hint style="info" %}
Add `build` to `.gitignore`.
{% endhint %}

We are going to upload this to S3.

### Creating a bucket

Search for `S3` then click the `Create bucket` button.

![](/files/-LrIz2aXYTQb1wj6-Xjc)

1. Follow the process, use the default values but untick `Block all public access`. (I like to name the bucket after the domain e.g daedalost.com)
2. Once the bucket is created, click on it to see the details
3. Go to `Permission` and paste the following:

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::NAME_OF_BUCKET/*"
            ]
        }
    ]
}
```

Replace `NAME_OF_BUCKET` with your bucket's name.

Everyone on the internet can now access this bucket.

![](/files/-LrJ-YI4kulH7kaE7Dj1)

{% hint style="info" %}
Later in the tutorial, we will use CloudFront to serve the assets from S3. Come back to this step and remove public access.&#x20;
{% endhint %}

{% hint style="info" %}
Have a look at the different [Bucket Policy Examples](https://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html) from the AWS documentation.
{% endhint %}

Go to `Properties` > `Static website hosting` > `Use this bucket to host a website`:

* Index document: `index.html`
* Error document: `index.html`

![](/files/-LrJ18KlWXWFjE4pHwPD)

This endpoint now serves the React app. It won't work at the moment since it doesn't connect to our API. Though, you can hard code `http://localhost:8080` as the endpoint to test it locally.


# Route53

We are going to use [Route53](https://aws.amazon.com/route53/) to buy and manage the domain.

I choose daedalost.com,  after Daedalus who was a craftsman and artist in Greek mythology.

Search for the domain you want to buy and follow the steps.

![](/files/-LrFS5b562ctLiBh7vfj)

![](/files/-LrFSEXvt8i5XjQe2sn7)


# CloudFront

We are going to use [CloudFormation](https://aws.amazon.com/cloudfront/) to route the traffic to the relevant resources.

* `/` will go to S3 to serve the React app static files, and
* `/api` will go to the GraphQL API.

![](/files/-LrFYHYRkX9DW4BnqB6w)

### Creating a distribution

Go to `CloudFront` > `Create Distribution`:

![](/files/-LrFZBi-GMxJGx5vclH2)

Select `Web` as a delivery method and use the following configuration:

* Origin Domain Name: The S3 bucket with resources
* Viewer Protocol Policy: Redirect HTTP to HTTPS
* Alternate Domain Names: The domain you bought (e.g daedalost.com)
* SSL Certificate: Custom SSL Certificate and click "Request or Import a Certificate with ACM" then follow the wizard.

Once you distribution is created, click its ID to edit it.

* Origins and Origin Groups > Create Origin, paste the API Gateway URL.
* Paste the API Gateway URL

![You can find the API Gateway URL in Stages](/files/-LrFawgBjvpY_HFcAKhX)

* Once created, go to Behaviors > Create Behavior
* Path Pattern: `/api`
* Select the newly created origin
* Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
* Object Caching: Customize`0` everywhere.


# Serverless

We are going to deploy the API using the [Serverless Framework](https://serverless.com). The [documentation](https://serverless.com/framework/docs/) is excellent and covers everything we need. If you are not familiar with Serverless, try the [hello work](https://serverless.com/framework/docs/providers/aws/examples/hello-world/node/) example and the [user guide](https://serverless.com/framework/docs/providers/aws/guide/quick-start/).

In our setup, we will use 3 serverless plugins:

* [Typescript](https://github.com/prisma-labs/serverless-plugin-typescript) to transpile our code Javascript.
* [Offline](https://github.com/dherault/serverless-offline) to run the app locally. (Note: add `.build` to `.gitignore`)
* [SSM](https://github.com/janders223/serverless-offline-ssm#readme) to read variables from our `.env`
* [Finch](https://github.com/fernando-mc/serverless-finch) to deploy the static files to S3.

### Using Express with Serverless

We also need to change our `index.ts` file to use [aws-serverless-express](https://github.com/awslabs/aws-serverless-express).

```typescript
exports.handler = (event: any, context: any) => {
  bootstrap().then(() => {
    const { app } = createApp();
    const server = awsServerlessExpress.createServer(app);
    awsServerlessExpress.proxy(server, event, context);
  });
}
```

### Common problems

`Error: Cannot find module './migrations'`

Add `import './migration.ts'` to `src/inddex.ts` and create `` `migrations/index.ts` ``importing all migration files.

`handler 'server' returned a promise and also uses a callback!`

You can't use `async` with the main Serverless file. Use `.then` for the promises instead.


# Security

The database shouldn't be available on the internet. It should always be accessed via the API.\
This is the default configuration with Aurora, but this applies to any production database you'll run in the future.

To achieve this, we will use Subnets within our VPC.

### VPC

The VPC is the main container that contains everything for our app. If we had another app, it would be in another VPC.

![Always run different apps in their own VPCs to improve security.](/files/-LrJAWBcvy72okR_GOfA)

### Subnets

Subnets are used to separate resources within a VPC. You can have subnets accessible from the internet (public) and subnets only accessible internally with rules (private).

The rule are set using [Security Groups](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html). E.g `Resource A` can access `Resource B` on port `5432`.

The database will be in a private subnet,\
We are going to accept traffic from the internet via a public subnet and send it to our API.

### IP address whitelisting

When using a 3rd party, you should always have a [whitelist](https://en.wikipedia.org/wiki/Whitelisting) of IP addresses. This way, only the intended resources can access it.

When building a scalable infrastructure, you need to be able to add and remove instances when needed. But this also means, the IP addresses of the instances are random and can change anytime.

When using lambda, we simple don't have any IP address.

How do we know which IP address to whitelist then? We could whitelist all the [AWS IP addresses](https://ip-ranges.amazonaws.com/ip-ranges.json). It's a start but that's not enough.

The solution is to buy an [Elastic IP address](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) attached to an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html). Set the outbound traffic (such as request to 3rd parties) to go through the Internet Gateway and use its IP address.

### Network architecture

![](/files/-LrJJE-33BjvBEIQDpvC)

{% hint style="info" %}
More info:

* [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html)
* [How do I give internet access to my Lambda function in a VPC?](https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/)
* [VPC and subnets](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html#vpc-subnet-basics)
  {% endhint %}


# CloudFormation

Everything can be done in the UI on console.aws.amazon.com. But we want to automate as much as possible.

For this reason, we won't create any resources manually anymore.

We will use [CloudFormation](https://aws.amazon.com/cloudformation/) instead so we can keep the infrastructure configuration under version control.

{% hint style="info" %}
Read more [Infrastructure as code](https://en.wikipedia.org/wiki/Infrastructure_as_code).
{% endhint %}

* If something went wrong, CloudFormation will **automatically rollback** the changes.
* You can delete all the resources created with one click.
* Use [detect drift](https://aws.amazon.com/blogs/aws/new-cloudformation-drift-detection/) to see if something was changed manually instead of using CloudFormation.

![You can follow the progress for your changes](/files/-LrFcyFHPOAhROWGMoz3)

![You can delete all the resources created and detect drift](/files/-LrFdKr0x2lfkx1C-9kX)

### How to use CloudFormation

You can use `yml` or `json` files. For `yml` files, resources take the following format:

```
ResourceName:
    Type: service-provider::service-name::data-type-name
    Properties:
        ...
```

{% hint style="info" %}
You can find all the options available at [AWS Resource and Property Types Reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html).
{% endhint %}

Example to create an RDS database using [AWS::RDS::DBCluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html):

```yaml
ServerlessRDSCluster:
    Type: AWS::RDS::DBCluster
    Properties:
        Engine: aurora
        EngineMode: serverless
        Port: 3306
        DatabaseName: testdb
        MasterUsername: myuser
        MasterUserPassword: secret
        ScalingConfiguration:
            AutoPause: False
            MinCapacity: 2
            MaxCapacity: 256
```

### Creating the resources

We can manage CloudFormation resources from `serverless.yml`.

Here's the full file to create everything needed:

{% code title="serverless.yml" %}

```yaml
service: daedalost

provider:
  region: eu-west-1
  stage: ${opt:stage, 'dev'}
  name: aws
  runtime: nodejs10.x
  memorySize: 1024
  timeout: 30
  vpc:
    securityGroupIds:
      - Fn::GetAtt: [VPCStaticIP, DefaultSecurityGroup]
    subnetIds:
      - Ref: SubnetPrivate
      - Ref: SubnetPrivate2
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ec2:CreateNetworkInterface
        - ec2:DeleteNetworkInterface
        - ec2:DescribeNetworkInterfaces
      Resource: "*"
  environment:
    SERVERLESS_EXPRESS_PLATFORM: aws
    NODE_ENV: production
    STAGE: ${opt:stage, 'development'}
    POSTGRES_DATABASE: ${self:service}
    POSTGRES_HOST:
      Fn::GetAtt:
        - ServerlessRDSCluster
        - Endpoint.Address
    POSTGRES_USERNAME: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_USERNAME}
    POSTGRES_PASSWORD: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_PASSWORD}
    JWT_SECRET: ${ssm:/${self:service}/${self:provider.stage}/JWT_SECRET}

plugins:
  - serverless-plugin-typescript
  - serverless-offline
  - serverless-finch

custom:
  client:
    bucketName: daedalost
    distributionFolder: ../web/build
    errorDocument: index.html

functions:
  server:
    handler: server.handler
    events:
      - http:
          path: /{proxy+}
          method: any
          cors: true

resources:
  Resources:
    # Step 1: Create a new VPC
    VPCStaticIP:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: 11.0.0.0/16
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-vpc

    # Step 2: Create 2 Subnets
    SubnetPublic:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}a
        CidrBlock: 11.0.0.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-public-subnet
        VpcId:
          Ref: VPCStaticIP

    SubnetPrivate:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}b
        CidrBlock: 11.0.1.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-subnet-b
        VpcId:
          Ref: VPCStaticIP

    SubnetPrivate2:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: ${self:provider.region}c
        CidrBlock: 11.0.2.0/24
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-subnet-c
        VpcId:
          Ref: VPCStaticIP

    # Step 3: Create an Internet Gateway
    InternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-igw

    # Attach Internet Gateway to VPC
    VPCGatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
        InternetGatewayId:
          Ref: InternetGateway
        VpcId:
          Ref: VPCStaticIP

    # Step 4: Create a public Route Table and Assign it to our public route
    RouteTablePublic:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPCStaticIP
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-public-route

    RoutePublic:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId:
          Ref: InternetGateway
        RouteTableId:
          Ref: RouteTablePublic

    SubnetRouteTableAssociationPublic:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePublic
        SubnetId:
          Ref: SubnetPublic

    # Step 5: Create a NAT Gateway
    # Before creating NAT Gateway, we need to create Elastic IP with vpc scope
    EIP:
      Type: AWS::EC2::EIP
      Properties:
        Domain: vpc

    NatGateway:
      Type: AWS::EC2::NatGateway
      Properties:
        AllocationId:
          Fn::GetAtt: [EIP, AllocationId]
        SubnetId:
          Ref: SubnetPublic

    RouteTablePrivate:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPCStaticIP
        Tags:
          - Key: Name
            Value: ${self:service}-${self:provider.stage}-private-route

    RoutePrivate:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId:
          Ref: NatGateway
        RouteTableId:
          Ref: RouteTablePrivate

    SubnetRouteTableMainAssociationPrivate:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePrivate
        SubnetId:
          Ref: SubnetPrivate

    SubnetRouteTableMainAssociationPrivate2:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: RouteTablePrivate
        SubnetId:
          Ref: SubnetPrivate2

    DatabaseSubnetGroup:
      Type: AWS::RDS::DBSubnetGroup
      Properties:
        DBSubnetGroupDescription: Db subnet for ${self:service}
        SubnetIds:
          - Ref: SubnetPrivate
          - Ref: SubnetPrivate2

    RDSSecurityGroup:
      Type: "AWS::EC2::SecurityGroup"
      Properties:
        GroupDescription: SecurityGroup ${self:service}/${self:provider.stage}
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 3306
            ToPort: 3306
            SourceSecurityGroupId:
              Fn::GetAtt: [VPCStaticIP, DefaultSecurityGroup]
        VpcId:
          Ref: VPCStaticIP

    ServerlessRDSCluster:
      Type: AWS::RDS::DBCluster
      Properties:
        DBSubnetGroupName:
          Ref: DatabaseSubnetGroup
        Engine: aurora-postgresql
        EngineMode: serverless
        DatabaseName: ${self:service}
        MasterUsername: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_USERNAME}
        MasterUserPassword: ${ssm:/${self:service}/${self:provider.stage}/POSTGRES_PASSWORD}
        VpcSecurityGroupIds:
          - Ref: RDSSecurityGroup
        ScalingConfiguration:
          AutoPause: False
          MinCapacity: 2
          MaxCapacity: 256
```

{% endcode %}

{% hint style="info" %}
Read the documentation about [Serverless Variables](https://serverless.com/framework/docs/providers/aws/guide/variables/).
{% endhint %}

{% hint style="info" %}
Read serverless blog post about [Managing secrets, API keys and more with Serverless](https://serverless.com/blog/serverless-secrets-api-keys/).
{% endhint %}

{% hint style="info" %}
Configure the VPC manually with the console by following this step by step tutorial [Configure and Connect to Serverless MySQL Database](https://aws.amazon.com/getting-started/tutorials/configure-connect-serverless-mysql-database-aurora/).
{% endhint %}


# Conclusion

Let's review what we covered:

* Automatically run tests for each commits
* Safely store secrets
* Serve static files for our React app
* Buy a domain
* Redirect the traffic to S3 or the API based on the URL
* Deploy the API using the Serverless Framework
* Manage network to improve security
* Use CloudFormation to keep the infrastructure in version control

{% hint style="info" %}
[`aws`](https://github.com/florianherrengt/book-code/tree/aws) branch and [pull request](https://github.com/florianherrengt/book-code/pull/7) available on GitHub.
{% endhint %}


# Stripe payment

{% hint style="warning" %}
Work in progress
{% endhint %}


# File upload

{% hint style="warning" %}
Work in progress
{% endhint %}


