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. 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.
Add Typescript to Amplify Functions
The following steps are used with the Amplify CLI and will install Typescript within its function-folder and add types to your Lambda- function
# 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
{
"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
.
"scripts": {
"tsc": "tsc"
}
Now, let's fill the index.ts
with some logic.
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
# 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 in combination with Lerna as a popular tool set when using a Monorepo. These are indeed great tools but we will use PNPM 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.
# 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
.
βββ 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.
# 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
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.
# 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
// 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.
"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.
amplify push
The two functions should be now visible in your AWS Console.
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
There are some bundlers out there and I am pretty sure you all have heard of Webpack. Others like Snowpack or Parcel are also pretty popular.
But I really like esbuild which we will also use next.
# go to the function-folder level
# and install esbuild
pnpm install -D esbuild
Adding a new scripts
in the package.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
.
globaldatanet / amplify-add-fancy
A Plugin for AWS Amplify to make its function more fancy.
Amplify Add-Fancy
This is an AWS Amplify CLI - Plugin for making your function
fancier
by adding Typescript- Function or turning your function
- folder to a Monorepo.
Why
We all know the benefits of Typescript but AWS Amplify's CLI does not support that out-of-the-box.
Furthermore, when creating many functions, each time you might pull the same library for all your functions. The solution for that are Workspaces (aka multi-package repositories, multi-project repositories, or monolithic repositories).
How
Make sure you have an Amplify-Project up and running.
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.
? 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 Typescript-Function |
You can clone the project and add it as a amplify-plugin (I will release it soon)π
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.
? 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 as #Hacktoberfest. So any contribution is more than welcome π€
Thanks for reading and Happy Coding!
Top comments (3)
Thanks for the Post!
I thought giving this a try but was wondering how do you debug the code locally if it's minified?
Would
amplify function mock function{1,2}
just work with source maps and everything?I'm confused about function specific package.json's - looks like they are traveling from place to place :)...
How come that you have package.json in
functionName1/src1
directory if you moved it to the top-level function dir? - am I missing something?It's worth to note, that
amplify pull
removes everything except functions folders from function's top-level folder.