-
Notifications
You must be signed in to change notification settings - Fork 819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support TypeScript for backend functions #659
Comments
Really want this feature. I know I'm not adding much to the conversation but there needs to be options within the cli to scaffold typescript outputs for all aspects of an amplify solution. |
Have you considered using the "postinstall" hook in npm? I have not tried using it but am about to give it a shot. Amplify uses a "amplify function build" command and the docs say it calls |
EDIT: Having used this solution for a while... it's a little fragile. However, I think it is a promising concept and is a good place to start. I was able to create a simple typescript lambda function that imports and configures Amplify and invoke it locally using the This might look like a long procedure, but it should take about 2 minutes and once it is configured, it should be easy to maintain. While it isn't a native amplify plugin, it does feel like an npm-compliant solution, so I think it's a legitimate, stable way to do this. WARNING: THIS WILL OVERWRITE CODE IN YOUR FUNCTION'S "src" directory (eg. StrategyInside the lambda function scaffold (eg. Procedure
WARNING: THIS NEXT STEP WILL OVERWRITE CODE IN YOUR FUNCTION'S "src" directory (eg.
You might run into a typescript error issue with |
I noticed that you have to update package.json everytime before calling amplify function build for the typescript to compile into JS. I know we are working around this problem and it would be nice if Amplify CLI automatically compiles Typescript to JavaScript out of the box. |
Or if at least they provide us with a hook to run a npm script or similar... :) |
FWIW I am building an amplify app with Clojurescript and would love the option to easily write my node.js functions in the same language as the rest of my stack. If support for typescript is considered I hope with that would come in the form that supports all compile to js languages. |
This would be awesome to have. Lambda functions are hard to test locally (relevant: issue with amplify function invoke) and Typescript would at least create some more confidence that simple errors aren’t going to make it into the code. Also, natural next steps (I have an existing issue about sharing code between lambda functions), and, with Typescript in the lambda functions, there should also be an easy way to share types between server and client-side code (isomorphic types?), mainly for API calls. Maybe that’s as simple as them living in the lambda function and a lengthy import from the client code, e.g., |
@mrcoles You can actually use Typescript with the new Build options feature. Please take a look out here - https://aws-amplify.github.io/docs/cli-toolchain/usage#build-options |
@kaustavghosh06 cool, thanks for sharing this update! Does that mean this issue should be updated or are some more things in the works on this? Also, does this require a minimum Some things from reading that example:
|
Is it possible to test this locally? |
@kaustavghosh06 I had the exact same 3 problems as @mrcoles when reading the documentation. |
You can read about adding lambda functions in Typescript as a part of this blog - https://servicefull.cloud/blog/amplify-ts/ |
+1 |
Has anyone tried TypeScript recently in Amplify Lambda functions with Promises? I'm receiving errors that I can't get past. If I try to use Promise directly (e.g.
If I try to use async/await:
Even in the blog https://servicefull.cloud/blog/amplify-ts/, if I use that example (with their tsconfig.json) with the following code:
It throws the same error as the first one above. I've tried this with numerous tsconfig.json configurations and none of them seem to make a difference. |
Nevermind on the post above. Turns out that in the blog post https://servicefull.cloud/blog/amplify-ts/, the |
@kaustavghosh06 - is it possible to run this same behaviour when running |
|
FWIW, I used package script to achieve this for now, something along the lines of:
Then, because of Yarn workspaces, I can also import local shared packages - which, of course, get bundled in to the final index.js code. And, since the 'empty' |
My 2 cents on running amplify with Typescript... (be prepared for some code copy and pasting)
"workspaces": {
"packages": [
"amplify/backend/function/**/src"
],
"nohoist": [
"**"
]
},
{
"compilerOptions": {
"sourceMap": true,
"target": "es2017",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["node"],
"module": "commonjs",
"paths": {
},
"baseUrl": "."
},
"exclude": [
"node_modules",
"**/build",
"**/dist"
],
}
"Handler": "build/index.handler", inside the src folder for the lambda, edit the lambda package.json {
"name": "uniqueName",
"version": "2.0.0",
"description": "Lambda function generated by Amplify",
"main": "index.js",
"license": "Apache-2.0",
"dependencies": {
"source-map-support": "^0.5.16"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.36",
"@types/node": "^12.12.14",
"typescript": "^3.7.2"
}
}
inside the src folder for the lambda, add a tsconfig file that extends your root tsconfig: {
"extends": "../../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./build",
"rootDir": "./"
}
}
update your root package.json for the entire amplify project to compile your new lambda on push "scripts": {
"amplify:lambdaName": "cd amplify/backend/function/lambdaName/src && npx tsc"
},
inside the lib/nodejs file, edit the package.json to be like so {
"name": "uniqueName",
"version": "2.0.0",
"description": "Lambda function generated by Amplify",
"main": "index.js",
"license": "Apache-2.0",
"dependencies": {
"source-map-support": "^0.5.16"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.36",
"@types/node": "^12.12.14",
"typescript": "^3.7.2"
}
}
inside the lib/nodejs file, add a tsconfig.json file {
"extends": "../../../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./build",
"rootDir": "./",
"declaration": true
}
}
** note the additional declaration output ** edit your root package.json to build the lambda layer on amplify push by add this under the scripts section "amplify:layerName": "cd amplify/backend/function/layerName/lib/nodejs && npx tsc" edit your root tsconfig.json to add a reference to you declaration file from the lambda layer "paths": {
"/opt/nodejs/build/exampleOutputJsFileFromInLambdaLayerBuildFile": ["./amplify/backend/function/layerCommonDynamoDb/lib/nodejs/build/declarationFileNameFromLambda.d.ts"]
}, ** note: exampleOutputJsFileFromInLambdaLayerBuildFile is the final build file from your lambda layer... fileName is important as it has to match aws lambdas /opt/nodejs folder structure to work on AWS correctly otherwise your directory references will be wrong ** you can now use your lambda layer in your lmbda functions: import commonLib from '/opt/nodejs/build/exampleOutputJsFileFromInLambdaLayerBuildFile' I haven't tried using this with amplify mock, i think a lot of work needs to be done on the local amplify mocking / testing environment anyway so rather use a multi environment AWS workflow... add the sourcemap import to the top of your lambda as well for better debugging experience. |
@danielblignaut thank you so much. I had this all working in a very hacky manner. But this is clean and simple, appreciate the advice. |
I'm actually against adding this functionality until Typescript is supported natively by AWS Lambda- hear me out... The ideal scenario would be that typescript would be pushed up as is. We'd then get type support directly inside the Lambda editor on the AWS Console. It would also really suck to have Amplify spend time on this to then see Typescript support pop up in AWS Lambda. |
@r0zar has a point. I personally develop all my backend code as an independent typescript node package. You can then use all the things you like such as jest for unit testing. The lambda then installs that private node package and becomes a very thin javascript layer on top of that node package built in typescript. This has worked out well and lets us develop and test things independently of the amplify workflow. The package also becomes very re-usable for scenarios outside of amplify (such as our own cli to interact with our backend). |
@r0zar I think it's a very low / non-existant priority for the lambda team as there is no "typescript" runtime in reality and even if so, it would probably be less performant if you want to run something like ts-node v building your package and running it directly as building with webpack gives you tree shaking, and other features to increase speed and reduce package size. You can also get runtime type support (for stack traces) with source maps already. personally, as a typescript developer, I've moved away from Amplify and have instead adopted AWS CDK which is another infrastructure as code library written by the AWS team. This library is based in typescript (although there are other language variations) and all the latest features come to typescript first. I also use lerna for a mono-repo structure to hold all my lambda's and build their typescript packages independently. With CDK you can choose the file location of your lambda code so I just add my lambda's as dependency's to the cdk package and use require.resolve to find their built code in the node_modules and deploy. Honestly works like a dream, does not feel hacky at all and personally my experience with CDK as a typescript dev has been great especially on bigger projects. |
@danielblignaut, I am curious about your implementation, do you have an open source example? |
@danielblignaut thanks so much for your reply. I found it incredibly helpful. I am finally able to use my typescript lambda layer properly. Could you please elaborate on your final comment about not using this with amplify mock. (It doesn't work with amplify mock by the way, as soon as I import my lambda layer library and run So I am just wondering how you test your code before pushing to amplify. Do you still test it locally? Is there a way to test this locally? (A better way than Thanks again :) |
Hey @ziggy6792 , I can’t quite remember the reasoning behind the last comment. But to provide some further points:
EDIT: Here's the lambda layers issue which seems resolved now: #4963 |
Hey @danielblignaut Thanks so much for your very detailed reply and especially for pointing me towards CDK. I am still trying to setup a backend where I have several lambda functions which share a common lib (which will ultimately talk to dynamo-db). I got pretty far with CDK and tried to follow you instructions as best as I could. If I deploy this stack to AWS and run my However once again I have the problem of mocking locally. I have tried to use this method (which I found here) to mock locally (this method works fine when I don't import my common
But I get an error
My questions are...
Thanks a lot 👍 EDIT: I found this which is an example (using CDK) of how to setup shared code in a lambda layer (includes deploying to stack and testing locally). What do you think @danielblignaut? It seems pretty complicated to me but maybe I can get my head round it. |
Some feedback:
Here's an example CDK repo I've setup with 2 lambdas, a common library and a CDK project. Includes all the bells and whistles except multi-aws account deployment (point 2) and testing environment could use a lot of work. |
@danielblignaut wow this is incredible. |
I found the best way to test the lambda functions locally is to bypass trying to run them as lambdas completely... I know sounds crazy but stick with me! The key is really adding this to your jest.config.js to redirect the module imports on the js files in your backend folder
I then have a set up function called configEnv() that i call in beforeAll() setup function
and then I just set up a test:
Obviously this will run as integration tests hitting your actual servers so if you need to hit a local stack then change the configEnv(). Personally I think real integration tests are highly underrated but I will write a bunch of unit and component tests with the various parts mocked out and then leave one or two full integration tests in to run against a proper stack. This is great in amplify because you can use a sandbox environment for most stuff. I don't really like setting up a local stack to "emulate" because they rarely behave like a real environment and I find most time lost on projects is debugging issues with the testing environment rather than the actual code. Since taking this approach and dropping localstack/amplify mock etc. I have saved tons of time in debugging. This jest module redirect technique also works with layers etc. which the current amplify mock and such don't so this is much much easier. Also works well with typescript because you don't need to build the functions before you test locally if your jest is setup to support typescript. You only need your amplify build command to run tsc in packages.json in the root project - |
Some feedback on your project. Firstly thanks so much again. This is by far the best way I have seen to locally mock and test lambdas. My productivity is going to increase so much with this setup.
I guess the disadvantage of this is every-time I commit a change to my front my whole backend will be redeployed too (and vice versa).
I tried adding
To lambda-a |
no problem. Glad it's helping! I've built other tooling around appsync and local environment setup that I'm hoping to publicly share but there's a lot of work documentation that I need to create first. Glad to hear it's helping someone! Your comments:
When I have a chance I'll add my CDK pipeline tools to that project that may help as well. |
Awesome thanks 👍 |
I have done quite a lot of work in the foundations for my app running using cdk (setup some lambdas, api gateway, cognito user pool etc.. ) and I have a react frontend which can authenticate with cognito and then call my api. I have a frontend repo and a backend repo as you suggested and I would now like to setup a pipeline for both; so that changes merged to master are automatically deployed to dev and then prod (after a manual approval step). It's pretty clear to me how to setup the backend pipeline as it is just an extension of the starter project you already shared. However I am less clear about how to setup the frontend pipeline. Would you suggest a folder structure like this? So I would have 2 yarn workspaces
For me the complicated bit is going to be configuring the I can probably get this working on my own. Just wanted to know if I am on the right tracks here. Or is there a better/simpler solution? Also if you have an example of typescript react frontend that is deployed to S3 using a CDK pipeline that would be amazing. Thanks so much for your help so far. |
This seems like a fairly straight forward feature to add...isn't it just a different template in addition to the ones for nodeJS and python etc...someone just needs to provide the simplest typescript project that works on lambda and add it to the menu I would think? |
after seeing many of the approaches here (thanks to all for documenting them!), i’ve landed on a setup using the most important bits are the build script in each amplify function’s package.json, which runs tsc for type-checking then esbuild for transpiling / bundling (see the blog post for details on the esbuild options): "scripts": {
"build": "tsc -noEmit && esbuild *.ts --main-fields=module,main --bundle --platform=node --external:@aws-sdk/signature-v4-crt --outdir=./"
} and the per-function build script in the amplify root package.json: "scripts": {
"amplify:<functionName>": "cd amplify/backend/function/<functionName>/src && yarn && yarn build"
} i also use import { GetItemQuery, GetItemQueryVariables } from 'API.js';
import { getItem } from 'graphql/queries.js'; |
We typically use Webpack to bundle up the solution. Sharing our settings here as well: Root package.json:
The function's tsconfig.json:
Our webpack.config.ts for the function (note that this file is TypeScript as well):
The source files live in the |
+1 |
That's an interesting approach. How does it work? |
It works just as I described. Develop your backend code as an independent npm package, that is installed via a lambda layer with this package in the layer's package.json. |
Oh, right, I missed it was "private node package". Makes sense. |
Is your feature request related to a problem? Please describe.
I use the TypeScript generation features for my front-end client, but am frustrated that any of the Lambda functions I create via
amplify add api
oramplify add function
must be written in vanilla JavaScript. This also makes it very difficult to share code between the backend and frontend (note that many of the auto-generated files created byamplify cli
use syntax likeexport default blah
that is invalid for use in the backend files).Describe the solution you'd like
Switch to using
tsc
to compile the files found inamplify/backend/function/*/src/
so that we can opt-in to type-safety by changing our file extension from.js
to.ts
or.tsx
, similar to how Create React App has handled this in version 2.0.0+.Describe alternatives you've considered
Alternatively, add (or document) how we can hook into the build process for
amplify/backend/function/*
so we can inject a TypeScript transpilation step ourselves.The text was updated successfully, but these errors were encountered: