AWS Bites Podcast

36. What’s new for JavaScript developers on AWS?

Published 2022-05-12 - Listen on your favourite podcast player

There are lots of options for programming languages on AWS these days but one of the most popular ones remains JavaScript. In this episode of AWS Bites we discuss what it’s like to develop with JavaScript, Node.js and TypeScript on AWS and what’s new in this field.

We explore why you would choose JavaScript and what are the trade-offs that come with this choice. We present some of the main features of the all-new AWS SDK v3 for JavaScript. We discuss runtime support and tooling for AWS Lambda and finally some interesting developments in the JavaScript ecosystem for the cloud and AWS.

In this episode we mentioned the following resources:

Let's talk!

Do you agree with our opinions? Do you have interesting AWS questions you'd like us to chat about? Leave a comment on YouTube or connect with us on Twitter: @eoins, @loige.

Help us to make this transcription better! If you find an error, please submit a PR with your corrections.

Eoin: There are lots of options for programming languages on AWS these days, but one of the most popular ones remains JavaScript. Today, we're going to discuss what it's like to develop with JavaScript and Node.js on AWS and what's new in this field. So we're going to talk about why you'd use JavaScript and what are the trade-offs, what are the features of the all-new AWS SDK version 3. We'll talk about runtime support and tooling for Lambda, and we'll talk about all the new developments in the ecosystem. My name is Eoin, and I'm joined by Luciano, and this is AWS Bites. Luciano, I think you're well-placed to talk about this as a Node.js expert. You have your book, the Node.js Design Patterns book, which is really popular. It's also very up-to-date on all the latest things you can do with Node. And not only that, you're also the author of the MIDI framework for Node.js on Lambda.

Luciano: Yeah, but I think... Thank you for doing all that advertisement for me. Always appreciated. But I think you also deserve a little bit of advertisement because you are the co-author of this book, AI as a Service, which is effectively a book that talks about serverless on AWS. But I was particularly impressed by the choice of using JavaScript as basically the main language for all the code examples. So I think you are also heavily invested in the JavaScript ecosystem when it comes to AWS. So maybe I would like to start by getting your opinion on why do you think that, yeah, it's actually a good choice, or not maybe, to use JavaScript on AWS.

Eoin: Yeah, well, I mean, both of us use JavaScript pretty heavily on AWS, but we're also fairly well used to using Python and other languages. I guess the thing about JavaScript is that it's well supported and widely used in all the tools and tutorials, so it's a pretty safe choice, especially for the serverless type of development. It's also, of course, the fact that full-stack JavaScript if you're doing front-end work as well, it means you have a more reasonable skills requirement and you can do JavaScript all the way down. But of course, there are trade-offs. Generally, I think JavaScript gives you a pretty good balance between performance and speed of iteration as a developer. And it's also just a really good fit for AWS Lambda. Maybe we could talk about that a little bit later. But given that we're trying to focus on a lot of the new developments in the ecosystem and on AWS, I know that you've done a lot of work recently with the new AWS SDK, which is version 3, and it's a complete rewrite. So what's all of that about and what's new for everybody?

Luciano: Maybe a lot of work is an overstatement, but I did give it a spin and I quite like it. So I'd like to share a few things that I was positively impressed by. Well, first of all is that you get very good support for both JavaScript and TypeScript, meaning that all the functions and methods that you use have good types. So if you use TypeScript, you get that advantage, auto-completion, type checking and so on.

But of course, you can use it also in plain JavaScript and everything works as expected. One interesting change, and this is pretty big, I think people will see the difference in just writing even the simplest integration, I don't know, writing a file to S3 or sending a message to a queue, is that now the entire API is using a pattern called the command pattern. So you don't... you instantiate a client, let's say an S3 client, and then rather than saying client.putObject, for instance, if you want to write to S3, now you have to create a put object command and then you can send that command through the client.

And this is, I guess, a little bit unusual at first when you come from the version of the API, but there are some advantages. First of all, that you can import only the commands that you actually need to use. And we'll talk a little bit more about tree-shaking, but that's something advantageous if you want to bundle your code and do tree-shaking. And also you get good typing for every single command.

So it's basically an easier way to just define the kind of action that you want to use and understand how to use them. And we'll put links in the show notes if you want to find the documentation and understand more how that changes the way you write your code. Other interesting things, and these, to be fair, to some extent were available also in V2, but I think that they have improved the level of support in V3, is that the JavaScript SDK in general tends to be very idiomatic, like rather than just being... I don't know, you don't get the feeling that you are using a pound-for-pine conversion of a Java client or something like that.

You really get the feeling that that library was natively written for JavaScript, even though if you look closely into the code, most of the code is automatically generated by more high-level definition of the AWS APIs. But still, I think the developers put a lot of effort to make sure that the code generated is actually as idiomatic as it could be for JavaScript developers. And two things that I really like in that sense are support for Node.js streams in a bunch of places.

The best example to me is S3, because very often you need to read or write files that could be big enough in the order of gigabytes. And if you've done any Node.js, you know that you have an odd limit when it comes to two gigabytes. You cannot put all the stuff in memory in one go. So you need to find other ways to deal with a lot of data. And the canonical way is to use Node.js streams. So when you do a strip put object, the body of that request could be a stream.

So you could be generating data at runtime, or you could even process data at runtime. A very good example is you're trying to write something to S3 and you maybe want to compress it and maybe even encrypt it as well. You could do all of that in a streaming fashion and as you write, do all the processing to compress and encrypt. And similarly, when you read from S3, you can read as a stream. So that allows you to, as soon as you consume the first few bytes of the file, you can already start to process them, send them somewhere else, build pipelines based on the data you're fetching.

So that can also give you a good boost in performance because you're not waiting to load the entire file before starting the processing. Another interesting one is support for async iterators, which comes very handy, especially when it comes to pagination. And there is an entire API for pagination. And I don't know, the classic example could be DynamoDB, right? You want to scan a table, you're going to be doing that in pages, I suppose.

You build a paginator and it gets the first few records. Then if you want more, you go to the next page and you keep going that way. There is one construct when you use async iterators, which is the for-await loop. So if you've never seen it, it looks like for-await const page of an object, maybe a paginator. And that basically creates a loop for you, where inside the loop you have this concept of a page.

But the loop, even though it's an asynchronous loop, it's kind of blocking in the sense that it's not going to continue the iteration until the next page is available. So it's managing all that asynchronicity for you. And your code looks synchronous, even though you are still taking advantage of the asynchronous model of JavaScript. So that's something that I really like and also allows you to handle asynchronous error with try-catch blocks, so makes also error handling much easier.

And one last note that I have about the SDK v3 is that there is built-in support for mocking, which comes very convenient when you want to do unit tests. Now, built-in is a little bit of an overstatement. It's an additional library that you need to install, but that library is maintained by AWS, and I'm quite sure they tried to keep it in sync with the evolution of the SDK. But the cool thing is that once you import this library, you basically get a set of mock objects that you can import for every single client, and then you can easily specify in your test.

If your test, for instance, if your code depends on, let's say, I don't know, SQS, you can say, OK, create a mock SQS client and simulate this particular behavior. Maybe it fails to write, maybe returns a specific response. So all this stuff is very useful, and before, with the previous version of the client, you were a little bit on your own to recreate all these types in your test and simulate those behavior.

Oh, yeah, there is one more thing that I almost forgot. There is another additional feature, which is something we saw in Python before, but in Python, it's not well-documented, I have to say. But it's basically the ability to create custom logic that can run before or after the client actually sends the HTTP request to AWS. So in the SDK v3, they actually formalized a little bit this thing into the middleware pattern.

So basically, you can write code that wraps around, let's say, the HTTP request going to AWS, and you can use that to either enhance the request going to AWS or manipulate the response coming back to AWS. So you can add behaviors like, I don't know, add compression to a message that is going to AWS, and that can be convenient, for instance. Something we mentioned in a previous episode, when you call CloudWatch put metric data, you have very restrictive payload size limits. So in that case, you can leverage compression to go a little bit over those limits to be able to push more data while staying between the boundaries. So that's something really cool. I haven't used it yet, but I had a look at the documentation, and it could be useful in cases like this. Yeah, that's a big spiel about the SDK v3. Maybe we should talk about serverless and Lambda in general.

Eoin: For Lambda, yeah, I think we mentioned that Node.js is a good choice for runtime on Lambda. It's been around for a very long time. It's been supported from the very beginning with Node.js. So it's, I would say, a first-class citizen, although you have lots of options these days, as well as custom runtimes. It's also one of the most performant runtimes, especially for scripting languages. So you'll get good cold start times.

And there's lots of useful benchmarks out there on Lambda cold start times in general for lots of different languages. I think, you know, languages like Go and Rust are almost leading the way in terms of compiled languages, but you get very good performance, and I have to say I'm pretty pleased with it in general. And there's lots of ways you can optimize it as well when it comes to squeezing those last few tens of milliseconds out of your cold start times.

So we can link, actually, in the show notes to some really good benchmarks that you can take a look at. Some of the things that you've mentioned there, there's some new language features. Like we have ESM modules now. So we've moved away from common JS modules to ESM modules with Node.js, and they've been supported since Node 14. I think there was a little bit of a bumpy road in terms of supporting them when Node.js 14 support was added to Lambda, but it is possible now.

And you can also do things like top level await. So I think async await is very widely adopted and almost de facto at this stage for asynchronous programming in Node.js, but one of the things in Lambda handler you'd like to do often is fetch some maybe configuration parameters from SSM parameter store outside of your handler logic so that it's done in the cold start phase and you don't incur the penalty for each event.

And that's something you can now do with async await with, you know, if you're using the AWS SDK outside of your handler functions, that's pretty nice. I also believe that support for Node.js is this is just like hot off the press. It's either on its way. I don't think it's been officially announced by Node.js, but I can see that, you know, Vercel have added their functions built on Lambda and it's serverless framework has added a support and that's in mainline now as well. So you can actually deploy server Node.js 16 functions as of today. We've also got lots of, I suppose, ecosystem support within Lambda as well. So we mentioned that one of the things you did a number of years ago and has been really growing at pace since is create the MIDI framework, which is a middleware engine for AWS Lambda written in Node.js. Do you want to give us a brief update, a brief explainer for people who haven't encountered MIDI? What kind of problems does it solve? Why is it useful in Lambda?

Luciano: Yeah, absolutely. So when I started to write my very first few Lambdas, I realized that because you have this such small unit of code, like you think more in functions rather than services, most of that, when you're actually writing the code, what tends to happen is that you have a little bit of boilerplate always before and after your main business logic for that Lambda function. So that boilerplate could be for instance, doing authentication, doing validation of the input, maybe fetching configuration from SSM and then eventually you get to run your actual business logic and then you might be ready to produce a response, but you might want to do a few additional things before the response is sent back to the user, maybe normalize that response.

Can make sense if you're using the HTTP integration with API gateway or you want to have error handling in a more generalized way. So what I realized after probably the first year that I was writing Lambdas for a project is, okay, I have all the Lambdas where it's very hard to tell what's the actual business logic and what's the boilerplate around it. And I was told that the whole promise around Lambdas was to focus more on the business logic.

So I was a little bit disappointed in that sense. And with the team I was working with at the time, we figured out that we would use the middleware pattern to try to isolate a little bit more all this boilerplate code into their own function and then keep your handler as pure as possible, so just the business logic, and then use the handler as a function where you could attach the boilerplate behavior that you wanted. But basically the function will read as everything is clean, everything has been already done for you, just do the business logic, and then you attach everything else in another place. And that also makes these behaviors, these additional behaviors or boilerplate if you want, more isolated and therefore testable, reusable, and so on. So that's kind of the use case for MIDI, and MIDI is basically a framework that allows you to do this thing in an easy way, just focus on the business logic and attach the additional behavior afterwards.

Eoin: Yeah, I remember being in that situation where you had all this boilerplate to parse your HTTP body and then create the status code and the headers on the way out. It was really nice to be able to just add in MIDI and solve a lot of those problems with the middlewares that you can add in from the community. I also know that this is part of something new, which is the Lambda Power Tools for TypeScript. And we might've mentioned the Lambda Power Tools for Python a few times on previous episodes. I believe the TypeScript version is due to come out in its stable version very soon, version 1.0. It has been available as a preview beta for some time. That builds on MIDI and also allows you to do lots of stuff like logging metrics and traces as well for your Node.js Lambdas. So we might put a link to that in the show notes as well and people can check it out.

Luciano: Yeah, absolutely. And I think it's another very useful utility when you care about making sure that your Lambdas are basically production ready. Libraries like Power Tools can make that process much easier because they have baked in a lot of defaults that you don't have to rebuild on your own basically.

Eoin: Yeah. And speaking of TypeScript, because that's called Lambda Power Tools for TypeScript, even though it works with JavaScript as well, there may be a question people will have is, should I use JavaScript or TypeScript when I'm building Node.js functions on Lambda? And even within JavaScript, do we just deploy vanilla raw JavaScript to our Lambda functions or should we be using some sort of bundling like esbuild or webpack to optimize the functions in the deployment package? What do you think?

Luciano: Yeah, this is very opinionated, because I think there are three types of people when it comes to TypeScript, three types of JavaScript developer, I would like to say. One that is like always use TypeScript. The other one is like, no, no, TypeScript is a bad idea. Always use poor JavaScript. And then there is the camp in between like, yes, use TypeScript, but not always. I am a little bit in that kind of moderate ground where I like TypeScript, but with moderation.

I think the really good use case for TypeScript is when you do a lot of complex data manipulation. So if you put the effort in defining all the types, then you definitely get the advantage that every time you are manipulating the data, the type checker can prevent you from doing mistakes. And this is something that happens all the time, like a type of undefined, and you need to do all these checks.

You cannot call undefined functions. All this kind of problems will go away in the sense that the TypeScript compiler, if you did a good job with your typings, will tell you before you actually run your code that there might be something wrong. So definitely use TypeScript when you are in this kind of situation doing a lot of heavy data transformation where you have complex types, which I think in Lambda happens a lot because most of the time you are writing glue code. So often you need to kind of map types from one system into type for another system.

Eoin: Yeah, I've seen that. And sometimes it's useful to have, if you're doing HTTP proxy integration, having the types for the event that you know you'll receive, that can be very useful, but I guess you can take advantage of those even if you're using JavaScript. I'm somewhat conflicted when it comes to using TypeScript in Lambda because I often think Lambda functions should be small and simple as possible.

And if you need to have that level of typing support, maybe you have too much complexity, but that can't be a hard and fast rule because it's not always that simple and you can benefit from those typings in some cases. But even with bundling, I often think bundling adds an extra bit of development tooling complexity. So if you want to reduce your package size, you can definitely benefit from bundling, but that means now you've got a minified JavaScript running and you need to make sure that your stack traces are readable from a developer when you're troubleshooting.

So you need to ensure that you've got source map support in there and all of that stuff, as well as the fact that bundling adds a little bit of a latency to your build. So it's a trade-off, right? There was a really good benchmark exercise that Yan Shui did about AWS Lambda performance and the trade-offs with adding bundling support and how that benefits your cold start time. So there is a benefit there.

It's just a question of what's the penalty you incur at build time as a developer. How does that matter to you? Often it depends on how fast your developer machine is even. So there's lots of different factors to weigh up there. At this point, should we talk about some of the other things, parts of the ecosystem that are relevant for JavaScript and Node.js developers? I think we mentioned one of the statistics recently about 70% of CloudFormation being deployed by the serverless framework. So serverless framework is pretty ubiquitous when it comes to building and deploying Lambdas. It's written in Node.js itself. Have you found as well that writing Node.js functions in serverless framework is always a little bit better supported or feels more like a first class citizen compared to writing other languages and deploying them with the serverless framework? I think so.

Luciano: And I mean, the first reason is because I think the documentation is probably the first one to come out every day. There's a new feature, I think, that the first thing that they publish is the JavaScript version. So maybe that's one reason, but I suppose because the tool itself is written in JavaScript. Probably the majority of developers in the team know JavaScript better than other languages.

Eoin: Yeah, and from a packaging perspective, it kind of integrates seamlessly with NPM and your Node modules directory and you don't need additional tooling to package up your zip. Exactly. I guess that makes sense. One of the other tools, of course, that's well supported with JavaScript and TypeScript is CDK. So we've covered that CDK in a previous episode in reasonable detail. And although it supports a lot of languages, I think it's a little bit more user friendly or developer friendly when you're using JavaScript and TypeScript. Because I believe the way it's written with the JSII library is that it's essentially a TypeScript implementation with bindings for other languages. So I think it's, yeah, it's a good option. I really actually like one of the areas where I really enjoy using TypeScript is when I'm using CDK because the tooling integration is so seamless.

Luciano: Yeah, there is another tool that I would like to give it a shout out even though it's not exclusively something that you will use in AWS. It's kind of an interesting project from Google. It's called ZX. And the idea is that sometimes writing bash scripts is tedious or at least I would agree with that statement. Maybe not everybody agrees with it. So the idea is like, okay, because JavaScript and Node.js are becoming more and more ubiquitous and you probably use them in different parts of your application.

What if you could write scripts that are JavaScript whereas in the past you would have been using bash? So the idea is how do we make that easier for people? Because of course you could have done that all the time, because at the end of the day, those are just scripts and you could do whatever you want with those scripts. But I suppose the advantage of bash is that it's very easy to run all their commands, all their processes with what we have in the system calling all their executables.

So what ZX did, and this is probably the primary features, they made very easy for you to do that in a JavaScript language effectively. So you can use dollar and backticks. So they are basically exploiting the feature of template literal strings. And what they say is, okay, if you tag a literal string with the dollar, then we are gonna execute the string for you as a command in the system and give you back a promise that tracks the execution of that command. So that way you can easily write a script in Node.js effectively, but also call other processes with like one liner. So that seems to be kind of a decent replacement for bash. And I suppose in the context of doing automation and deploying projects to the cloud, that could be something convenient if you really want to be like 100% JavaScript end to end.

Eoin: And that's pretty nice. How does that work then with, because one of the primary features of Node.js is it's asynchronous nature and you have either callbacks or promises and await. How does that fit into ZX?

Luciano: So basically you, it's like you have top level await out of the box. Actually you effectively have top level await out of the box. So you, every time you call a command with this kind of dollar backtick syntax, you need to of course await that command. If you care about our result before you continue your execution, you could also use that for instance with promise all, if you want to kind of do a bunch of things concurrently. So it just makes it very nice to, if you are used to of course the JavaScript patterns, because if you are more used to bash than to JavaScript, I would probably recommend stick to bash. But if you're, exactly. But if you're more used to JavaScript, you get something that is as close as possible to the experience of writing bash while keeping the JavaScript syntax.

Eoin: That's really good. Okay, well, maybe that's a good point to wrap up. It might be useful if people have other suggestions around great tips, tooling, improvements in JavaScript that are relevant for AWS developers. Let us know in the comments. And you can also reach out to us on Twitter to either Luciano or myself. Our links are all in the show notes. And maybe the last thing I do is just point people back to episode four. We discussed, I suppose the trade-offs of using all the different languages for AWS Lambda, including JavaScript, but all the other options as well. So feel free to check that out and we'll see you in the next episode. be. Thanks for watching!