How to add fancy to your Amplify Project
Tags: aws, monorepo, typescript, hacktoberfest
Published: 2021-10-11 07:38:20

---
series: AWS Amplify
---

In this part of the series, we want to make your Amplify project more fancy by adding Typescript to your Amplify functions and/or to turn your `function`- folder to a Monorepo. Just fancy sh*t ;)

This post also contains a project for #Hacktoberfest 🥳

## Why Typescript

We all know the [benefits of Typescript](https://www.typescriptlang.org/why-create-typescript). If you want to know more about Typescript, I encourage you to read the provided link.

## Why Monorepo

*Monorepo* aka *Multi-Project repository* aka *monolithic repositories* is useful when synchronise packages along the functions. Monorepo also allow to share libraries across them.
As mentioned in the previous series, each function could be seen as package containing its own `node_modules`. 
The following illustration hopefully demonstrates the problem.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0cghcnf1rpce1msy00fk.png)

## Add Typescript to Amplify Functions

The following steps are used with the [Amplify CLI](https://docs.amplify.aws/cli/start/install/) and will install Typescript within its function-folder and add types to your Lambda- function

```bash
# Add a Lambda Function
amplify add function

? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: concatenate
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

# Go the the src-Folder of the Function
cd amplify/backend/function/functionName/src

# Remove the replace .js with .ts
mv index.js index.ts

# Install Typescript and AWS SDK
npm install -D typescript
# Install AWS Lambda for better Typescript Handling
npm install aws-lambda 
# We still need some types
npm install -D @types/aws-lambda @types/node

# Add the Typescript configuration
touch tsconfig.json
```

Now, we have to fill the `tsconfig.json`

```json
{
    "compilerOptions": {
      "declaration": false,
      "target": "ES2017",
      "module": "commonjs",
      "moduleResolution": "node",
      "rootDir": ".",
      "lib": ["es2017"],
      "typeRoots": ["node_modules/@types"],
      "esModuleInterop": true,
      "allowSyntheticDefaultImports": true,
      "noImplicitAny": true,
      "sourceMap": false,
      "strict": true,
      "baseUrl": ".",
      "outDir": "./"
    },
    "include": ["./**/*"],
    "exclude": ["node_modules"]
}
```

Now, we add a script to the `packages.json`.

```json
"scripts": {
    "tsc": "tsc"
  }
```

Now, let's fill the `index.ts` with some logic.

```typescript
import { Callback, Context, Handler } from 'aws-lambda';

interface TriggerEvent {
  key1: string;
  key2: string;
  key3: string;
}
  
export const handler: Handler<TriggerEvent, string> = (event: TriggerEvent, context: Context, callback: Callback<string>) => {
  const concatKey = `${event.key1} ${event.key2} ${event.key3}`;
  callback(null, concatKey);
};
```

If you use VS Code, you should make use of the Autocompletion by simply pressing `.` or make use of the `aws-lambda`- module by changing custom `TriggerEvent` according to your trigger (e.g. `SQSEvent`,or `DynamoDBStreamEvent` etc.).
By running `npm run tsc` it should generate an `index.js`.
We could test in Amplify

```bash
# I had name my function 'concatenate'
amplify mock function concatenate
```

Okay, cool now you know how to add Typescript to your Amplify Function.

We can create more... but wait... do we have to this all over again? Isn't that sort of inefficient?

If we create a new function, we would basically copy&paste the `package.json` and `tsconfig.json` in the new function. We would have x-times `node_modules` depending on the number of functions with x-times `typescript` and `aws-lambda` - module.

I hope you see the problem 😉
That is why, we should consider using a Monorepo.

## Create a Monorepo for your Amplify Functions

Probably, you have heard about [Yarn Workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) in combination with [Lerna](https://lerna.js.org/) as a popular tool set when using a Monorepo. These are indeed great tools but we will use [PNPM](https://pnpm.io/) instead.
PNPM has not just a built-in support for multiple packages but also handles all your `node_modules` from your computer efficiently. Generally, you should use it for all your Node-Projects. Your Disk Drive will thank you for that 😁

Okay, let's continue.
First delete the `node_modules`- Folder. Please also remove the `package-lock.json`.
Then we will move the `package.json` and `tsconfig.json` the top-level of the function - folder.

```bash
# Install PNPM if you haven't
npm install -g pnpm

# You should still be in the your-fullstack-app/amplify/backend/function/functionName/src
rm -rf node_modules package-lock.json

# Move the packages.json and tsconfig.json to the function-folder
mv package.json tsconfig.json ../../

# And let's create a new Function and change the index.js to index.ts
amplify add function
```

Your folder structure should look like this

```bash
.
├── functionName1
│   ├── amplify.state
│   ├── functionName1-cloudformation-template.json
│   ├── function-parameters.json
│   └── src
│       ├── event.json
│       ├── index.ts
│       └── package.json
├── functionName2
│   ├── amplify.state
│   ├── function-parameters.json
│   ├── functionName2-cloudformation-template.json
│   └── src
│       ├── event.json
│       ├── index.ts
│       └── package.json
├── package.json
└── tsconfig.json
```

Due to the nature of Amplify, the `src`- Folder will contain the resulting Lambda. If you have chosen a different `srcDir` when doing `amplify init` or `amplify pull` then that will be your folder. Here, I will use `src`.

For our second function, we will install a new package. But before we install the modules, we will move the content of `src` a level up.

```bash
# Make sure you are at 'functionName2'- level
mv src/** .
# Install i18n
pnpm install i18n
# Install its types
pnpm install -D @types/i18n

# Let's make that function work
cd src && mkdir locales
echo "{ \"title\": \"Translation\" }" > locales/en.json
echo "{ \"title\": \"Traduction\" }" > locales/fr.json
```

Fill the `index.ts` up

```typescript
import { Callback, Context, Handler } from 'aws-lambda';
import i18n from 'i18n';

interface TriggerEvent {
  lang: string;
}export const handler: Handler<TriggerEvent, string> = (event: TriggerEvent, context: Context, callback: Callback<string>) => {
  // init i18n
  i18n.configure({
    defaultLocale: 'en',
    directory: __dirname + '/locales',
  });
  // set locale according to event
  i18n.setLocale(event.lang);  // return the translation
  callback(null, i18n.__('title'));
};
```

If you use VS Code, you probably notice the red squiggle under `aws-lambda`and `__dirname` . That is because, we haven't installed the modules and the respective types.

Okay, install the modules globally for the functions.

```bash
# Go the function top-level folder
cd ../
# Install Modules using PNPM
pnpm install
```

After that the red squiggles are gone 👌
So what happen if we run `pnpm tsc`?
Right, all the functions should have an index.js now. But.. wait... the src- Folder is empty.

Not a big problem because PNPM can run all script of all the `package.json` within the same Monorepo.
Alright, let's start

```bash
// in top-level package.json
"scripts": {
    "mv": "tsc && pnpm move -r", // this is now executing all package.json with 'mv'- script
    "tsc": "tsc",
 }
```

We added now a `mv`- script to use PNPM for executing in all functions folder the `move` - script in their respective `package.json`.
Yet the `package.json` of the functions.

```json
"scripts": {
  "move": "mv index.js src"
}
```

Basically, with that script, we are moving the `index.js` to the `src`- folder.
Let's test it by running `pnpm mv`. Et voilà, your `src`- folder has only files which should be uploaded.
You could run `amplify mock function functionName{1,2}` in order to see if they are working.
Now, it is time to push it to the cloud.

```bash
amplify push
```

The two functions should be now visible in your AWS Console.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jk62l2k414qqrl6enu0r.png)
 
Cool!
Try to test the Lambda within your AWS Console.
What? It doesn't work??🤨
You are saying the dependencies are missing?
Oh, so we should have put the `node_modules`- folder into the `src`, is that what you are saying? That is inacceptable, because it might be that modules are too big and we should keep the Lambda as tiny as possible otherwise the cold start would be too long...
But I got that covered. Another fancy thing we could do: Bundling the modules in our `index.js`🦸‍♀️🦸🦸‍♂️

## Bundling your Amplify Function

Why should we bundle and minify our code? Well, simply we reduce the Lambda package size by removing unnecessary characters and bundle the used modules in one place. Learn more about [bundling code](https://www.freecodecamp.org/news/javascript-modules-part-2-module-bundling-5020383cf306/)

There are some bundlers out there and I am pretty sure you all have heard of [Webpack](https://webpack.js.org/). Others like [Snowpack](https://www.snowpack.dev/) or [Parcel](https://parceljs.org/) are also pretty popular.
But I really like [esbuild](https://esbuild.github.io/) which we will also use next.

```bash
# go to the function-folder level
# and install esbuild
pnpm install -D esbuild
```

Adding a new `scripts` in the `package.json`

```json
"scripts": {
  "tsc": "tsc",
	"mv": "tsc && pnpm move -r",
  "build": "esbuild ./**/src/index.ts --bundle --minify --platform=node --outdir=./ && pnpm move -r"
}
```

Shortly said, it bundles and minifies all the `index.ts`-files (those are your entrypoint) of the functions to the latest `index.js` - node version.
You should know see an `index.js` with a single long line of code. 
Great it looks good! Let's push it again `amplify push`.
Now, test again and yes! It works🥳

## `add-fancy`- Plugin and celebrating Hacktoberfest

Puh, that was actually a lot..
Here comes the best part. I have put that all into plugin, I called it [`amplify-add-fancy`](https://github.com/globaldatanet/amplify-add-fancy).

{% github globaldatanet/amplify-add-fancy %}

You can clone the project and add it as a amplify-plugin (I will release it soon)🚀

```bash
git clone https://github.com/globaldatanet/amplify-add-fancy.git
amplify plugin add
```

You get prompted where you need to put your **absolute path** and enter `Yes` afterwards.

```bash
? Enter the absolute path for the root of the plugin directory:
/absolute/path/to/this/repository
? Run a fresh scan for plugins on the Amplify CLI pluggable platform Yes
```

Now you can use follow commands

| Command                        | Description                                                                                            |
| ------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `amplify add-fancy typescript` | Add a Single Typescript-Function                                                                              |
| `amplify add-fancy monorepo`   | Turns your `function`- folder into a Monorepo. **NOTE:** This makes sense with more than two functions |

Since it's #Hacktoberfest, I have [labeled some issues](https://github.com/globaldatanet/amplify-add-fancy/issues) as #Hacktoberfest. So any contribution is more than welcome 🤗 

Thanks for reading and Happy Coding!